<?php
namespace Aqarmap\Bundle\ListingBundle\Service;
use App\Entity\Lead\Lead;
use App\Message\Listing\SyncListingProjectMessage;
use Aqarmap\Bundle\CreditBundle\Constant\CreditDescriptions;
use Aqarmap\Bundle\CreditBundle\Constant\CreditStatus;
use Aqarmap\Bundle\CreditBundle\Entity\Credit;
use Aqarmap\Bundle\CreditBundle\Entity\Service;
use Aqarmap\Bundle\CreditBundle\Services\CreditManager;
use Aqarmap\Bundle\FeatureToggleBundle\Service\FeatureToggleManager;
use Aqarmap\Bundle\ListingBundle\Constant\CreditDescriptionTypes;
use Aqarmap\Bundle\ListingBundle\Constant\ImpressionTypes;
use Aqarmap\Bundle\ListingBundle\Constant\InteractionTypes;
use Aqarmap\Bundle\ListingBundle\Constant\ListingCategories;
use Aqarmap\Bundle\ListingBundle\Constant\ListingCustomFields;
use Aqarmap\Bundle\ListingBundle\Constant\ListingFeaturedStatus;
use Aqarmap\Bundle\ListingBundle\Constant\ListingFeaturedTypes;
use Aqarmap\Bundle\ListingBundle\Constant\ListingFeatures;
use Aqarmap\Bundle\ListingBundle\Constant\ListingFeesTypes;
use Aqarmap\Bundle\ListingBundle\Constant\ListingPendingStatus;
use Aqarmap\Bundle\ListingBundle\Constant\ListingSortingOptions;
use Aqarmap\Bundle\ListingBundle\Constant\ListingStatus;
use Aqarmap\Bundle\ListingBundle\Constant\MarketPropertyTypes;
use Aqarmap\Bundle\ListingBundle\Constant\PhotoTypes;
use Aqarmap\Bundle\ListingBundle\Contracts\PhoneManagerInterface;
use Aqarmap\Bundle\ListingBundle\Document\ListingActivityLog;
use Aqarmap\Bundle\ListingBundle\Entity\CallRequest;
use Aqarmap\Bundle\ListingBundle\Entity\Favourite;
use Aqarmap\Bundle\ListingBundle\Entity\File;
use Aqarmap\Bundle\ListingBundle\Entity\FreeListing;
use Aqarmap\Bundle\ListingBundle\Entity\Listing;
use Aqarmap\Bundle\ListingBundle\Entity\ListingAttribute;
use Aqarmap\Bundle\ListingBundle\Entity\ListingFeature;
use Aqarmap\Bundle\ListingBundle\Entity\ListingPhone;
use Aqarmap\Bundle\ListingBundle\Entity\ListingPhoto;
use Aqarmap\Bundle\ListingBundle\Entity\Location;
use Aqarmap\Bundle\ListingBundle\Entity\Photo;
use Aqarmap\Bundle\ListingBundle\Entity\PropertyType;
use Aqarmap\Bundle\ListingBundle\Entity\Rejection;
use Aqarmap\Bundle\ListingBundle\Entity\Section;
use Aqarmap\Bundle\ListingBundle\Event\ListingEvent;
use Aqarmap\Bundle\ListingBundle\Event\ListingFeatureEvent;
use Aqarmap\Bundle\ListingBundle\Message\ParentListingUpdated;
use Aqarmap\Bundle\ListingBundle\Model\Contracts\BumpUpModelInterface;
use Aqarmap\Bundle\ListingBundle\Model\LeadModel;
use Aqarmap\Bundle\ListingBundle\Model\ListingSortingOption;
use Aqarmap\Bundle\ListingBundle\Model\ListingSortingValues;
use Aqarmap\Bundle\ListingBundle\Model\NonListingLeadInterface;
use Aqarmap\Bundle\ListingBundle\Repository\CallRequestLeadRepository;
use Aqarmap\Bundle\ListingBundle\Repository\FavouriteRepository;
use Aqarmap\Bundle\ListingBundle\Repository\FreeListingRepository;
use Aqarmap\Bundle\ListingBundle\Repository\InteractionRepository;
use Aqarmap\Bundle\ListingBundle\Repository\ListingImpressionRepository;
use Aqarmap\Bundle\ListingBundle\Repository\ListingLeadRepository;
use Aqarmap\Bundle\ListingBundle\Repository\ListingPhotoRepository;
use Aqarmap\Bundle\ListingBundle\Repository\ListingRepository;
use Aqarmap\Bundle\ListingBundle\Repository\LocationRepository;
use Aqarmap\Bundle\ListingBundle\Repository\MessageLeadRepository;
use Aqarmap\Bundle\ListingBundle\Repository\PhoneLeadRepository;
use Aqarmap\Bundle\ListingBundle\Repository\PhoneRepository;
use Aqarmap\Bundle\ListingBundle\Repository\PropertyTypeRepository;
use Aqarmap\Bundle\ListingBundle\Repository\TopSellerLeadRepository;
use Aqarmap\Bundle\ListingBundle\Service\Contracts\ListingFeatureServiceInterface;
use Aqarmap\Bundle\ListingBundle\Service\V4\CostPerLeadService;
use Aqarmap\Bundle\MainBundle\Constant\ActivityType;
use Aqarmap\Bundle\MainBundle\Constant\CustomParagraphPlaceTypes;
use Aqarmap\Bundle\MainBundle\Constant\Locales;
use Aqarmap\Bundle\MainBundle\Constant\StreamingEventTopic;
use Aqarmap\Bundle\MainBundle\Contract\EventStreamingClientFactoryInterface;
use Aqarmap\Bundle\MainBundle\Contract\ProducerFactoryInterface;
use Aqarmap\Bundle\MainBundle\Model\Listing\V4\ListingDataMapper;
use Aqarmap\Bundle\MainBundle\Repository\CustomParagraphRepository;
use Aqarmap\Bundle\MainBundle\Service\ActivityLogger;
use Aqarmap\Bundle\MainBundle\Service\NumberToWord;
use Aqarmap\Bundle\MainBundle\Service\Setting;
use Aqarmap\Bundle\MessageBundle\Service\Composer;
use Aqarmap\Bundle\NeighborhoodBundle\Entity\LocationStatistics;
use Aqarmap\Bundle\NeighborhoodBundle\Service\LocationStatisticsManager;
use Aqarmap\Bundle\NotificationBundle\Events\ListingNotificationEvent;
use Aqarmap\Bundle\NotificationBundle\Types\ListingWasPublished;
use Aqarmap\Bundle\NotificationBundle\Types\ListingWasRejected;
use Aqarmap\Bundle\OTPBundle\Contract\OtpServiceInterface;
use Aqarmap\Bundle\SearchBundle\Model\ListingSearchFaqsCriteria;
use Aqarmap\Bundle\SearchBundle\Services\ListingFaqService;
use Aqarmap\Bundle\UserBundle\Constant\UserServicesType;
use Aqarmap\Bundle\UserBundle\Constant\UserTypes;
use Aqarmap\Bundle\UserBundle\Entity\User;
use Aqarmap\Bundle\UserBundle\Services\UserManager;
use Aqarmap\Bundle\UserBundle\Services\UserServicesManager;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Internal\Hydration\IterableResult;
use Doctrine\ORM\OptimisticLockException;
use Doctrine\ORM\ORMException;
use Doctrine\ORM\QueryBuilder;
use Gedmo\Translatable\TranslatableListener;
use JMS\Serializer\SerializationContext;
use JMS\Serializer\SerializerInterface;
use Knp\Bundle\PaginatorBundle\Pagination\SlidingPagination;
use Knp\Component\Pager\PaginatorInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class ListingManager
{
public const ALLOWED_STATUS_FOR_APPROVAL = [
ListingStatus::PENDING,
ListingStatus::PENDING_PAYMENT,
ListingStatus::REJECTED,
ListingStatus::PENDING_PHOTOS,
];
public const DECIMALS_PLACES_NUMBER = 2;
public const MAP_STRING_DAYS_WITH_NUMBER = [
'7Days' => 7,
'30Days' => 30,
'ALLTIME' => null,
'ONLYLIVE' => null,
];
public const TOP_PICKS_LABEL = 'topPicks';
public const PERCENTAGE_FACTOR = 100;
public const EXPIRATION_DATE_FORMAT = 'Y-m-d';
public const ADD_TEN_YEARS = '+10 years';
/**
* Month Range.
*
* @var int
*/
public const MONTH_RANGE = 4;
private const LISTING_COUNTER_CACHE_KEY = 'listing_counter';
/** @var ContainerInterface */
public $container;
protected $dispatcher;
/** @var EntityManagerInterface */
protected $em;
/** @var DocumentManager */
protected $dm;
/** @var EntityRepository */
protected $repo;
/** @var ListingRuleMatcher */
protected $matcher;
/** @var CreditManager */
protected $creditManager;
/** @var ActivityLogger */
protected $activityLogger;
/** @var TranslatorInterface */
protected $translator;
/** @var Listing */
protected $listing;
/** @var string */
protected $class;
/** @var ListingFeatureServiceInterface */
protected $listingFeatureService;
/** @var BumpUpModelInterface */
protected $pumpUpModel;
/** @var Setting */
protected $settings;
/** @var LocationStatisticsManager */
protected $locationStatisticsManager;
/** @var AuthorizationChecker */
protected $authorizationChecker;
/** @var TokenStorageInterface */
protected $tokenStorage;
protected UserManager $userManager;
/** @var UserServicesManager */
protected $userServicesManager;
/** @var LoggerInterface */
protected $logger;
/** @var EventStreamingClientFactoryInterface */
protected $eventStreamingFactory;
/** @var FeatureToggleManager */
protected $featureToggleManager;
/** @var CompoundStatusLogService */
protected $compoundStatusLogService;
/** @var Composer */
protected $messageComposer;
/** @var OtpServiceInterface */
protected $otpService;
/** @var LeadService */
protected $leadService;
/** @var ListingRepository */
protected $listingRepository;
/** @var ListingPhotoRepository */
protected $listingPhotoRepository;
/** @var InteractionRepository */
protected $interactionRepository;
/** @var LocationRepository */
protected $locationRepository;
/** @var FavouriteRepository */
protected $favouriteRepository;
/** @var PhoneRepository */
protected $phoneRepository;
/** @var CallRequestLeadRepository */
protected $callRequestLeadRepository;
/** @var PhoneLeadRepository */
protected $phoneLeadRepository;
/** @var MessageLeadRepository */
protected $messageLeadRepository;
/** @var FreeListingRepository */
protected $freeListingRepository;
/** @var RequestStack */
protected $requestStack;
/** @var TranslatableListener */
protected $translatableListener;
/** @var CallRequestManager */
protected $callRequestManager;
/** @var PhoneManagerInterface */
protected $phoneManager;
/** @var RouterInterface */
protected $router;
/** @var NumberToWord */
protected $numberToWordService;
/** @var PaginatorInterface */
protected $paginator;
/** @var ProducerFactoryInterface */
protected $producerFactory;
/** @var array */
protected $locales;
/**
* @var CostPerLeadService
*/
protected $costPerLeadService;
/**
* @var ListingImpressionRepository
*/
protected $listingImpressionRepository;
/** @var ListingContactRateService */
protected $listingContactRateService;
private $cache;
/**
* @var TopSellerLeadRepository
*/
protected $topSellerLeadRepository;
/**
* @var ListingLeadRepository
*/
protected $listingLeadRepository;
protected HttpClientInterface $searchClient;
protected MessageBusInterface $messageBus;
private EntityManagerInterface $entityManager;
/**
* @var SerializerInterface
*/
private $serializer;
/**
* @var CustomParagraphRepository
*/
private $customParagraphRepository;
/**
* @var ListingFaqService
*/
private $listingFaqService;
private PropertyTypeRepository $propertyTypeRepository;
public function __construct(
ContainerInterface $container,
EventDispatcherInterface $dispatcher,
EntityManagerInterface $em,
ListingRuleMatcher $matcher,
CreditManager $creditManager,
DocumentManager $dm,
ActivityLogger $activityLogger,
TranslatorInterface $translator,
ListingFeatureServiceInterface $listingFeatureService,
BumpUpModelInterface $pumpUpModel,
Setting $settings,
LocationStatisticsManager $locationStatisticsManager,
AuthorizationCheckerInterface $authorizationChecker,
TokenStorageInterface $tokenStorage,
UserServicesManager $userServicesManager,
LoggerInterface $logger,
EventStreamingClientFactoryInterface $eventStreamingFactory,
FeatureToggleManager $featureToggleManager,
CompoundStatusLogService $compoundStatusLogService,
Composer $messageComposer,
OtpServiceInterface $otpService,
ListingRepository $listingRepository,
ListingPhotoRepository $listingPhotoRepository,
InteractionRepository $interactionRepository,
LocationRepository $locationRepository,
PhoneRepository $phoneRepository,
FavouriteRepository $favouriteRepository,
CallRequestLeadRepository $callRequestLeadRepository,
PhoneLeadRepository $phoneLeadRepository,
MessageLeadRepository $messageLeadRepository,
FreeListingRepository $freeListingRepository,
RequestStack $requestStack,
TranslatableListener $translatableListener,
PhoneManagerInterface $phoneManager,
RouterInterface $router,
NumberToWord $numberToWordService,
PaginatorInterface $paginator,
ProducerFactoryInterface $producerFactory,
CostPerLeadService $costPerLeadService,
ListingImpressionRepository $listingImpressionRepository,
array $locales,
ListingContactRateService $listingContactRateService,
CacheInterface $cache,
ListingLeadRepository $listingLeadRepository,
TopSellerLeadRepository $topSellerLeadRepository,
HttpClientInterface $searchClient,
MessageBusInterface $messageBus,
EntityManagerInterface $entityManager,
SerializerInterface $serializer,
CustomParagraphRepository $customParagraphRepository,
ListingFaqService $listingFaqService,
PropertyTypeRepository $propertyTypeRepository
) {
$this->container = $container;
$this->dispatcher = $dispatcher;
$this->em = $em;
$this->dm = $dm;
$this->matcher = $matcher;
$this->creditManager = $creditManager;
$this->class = 'Aqarmap\Bundle\ListingBundle\Entity\Listing';
$this->listingRepository = $listingRepository;
$this->activityLogger = $activityLogger;
$this->translator = $translator;
$this->listingFeatureService = $listingFeatureService;
$this->pumpUpModel = $pumpUpModel;
$this->settings = $settings;
$this->locationStatisticsManager = $locationStatisticsManager;
$this->authorizationChecker = $authorizationChecker;
$this->tokenStorage = $tokenStorage;
$this->userServicesManager = $userServicesManager;
$this->logger = $logger;
$this->eventStreamingFactory = $eventStreamingFactory;
$this->featureToggleManager = $featureToggleManager;
$this->compoundStatusLogService = $compoundStatusLogService;
$this->messageComposer = $messageComposer;
$this->otpService = $otpService;
$this->listingPhotoRepository = $listingPhotoRepository;
$this->interactionRepository = $interactionRepository;
$this->locationRepository = $locationRepository;
$this->requestStack = $requestStack;
$this->favouriteRepository = $favouriteRepository;
$this->phoneRepository = $phoneRepository;
$this->callRequestLeadRepository = $callRequestLeadRepository;
$this->phoneLeadRepository = $phoneLeadRepository;
$this->messageLeadRepository = $messageLeadRepository;
$this->freeListingRepository = $freeListingRepository;
$this->translatableListener = $translatableListener;
$this->phoneManager = $phoneManager;
$this->router = $router;
$this->numberToWordService = $numberToWordService;
$this->paginator = $paginator;
$this->producerFactory = $producerFactory;
$this->costPerLeadService = $costPerLeadService;
$this->locales = $locales;
$this->listingImpressionRepository = $listingImpressionRepository;
$this->listingContactRateService = $listingContactRateService;
$this->cache = $cache;
$this->listingLeadRepository = $listingLeadRepository;
$this->topSellerLeadRepository = $topSellerLeadRepository;
$this->searchClient = $searchClient;
$this->messageBus = $messageBus;
$this->entityManager = $entityManager;
$this->serializer = $serializer;
$this->customParagraphRepository = $customParagraphRepository;
$this->listingFaqService = $listingFaqService;
$this->propertyTypeRepository = $propertyTypeRepository;
}
/**
* @return Listing
*/
public function createListing()
{
$class = $this->class;
return new $class();
}
/**
* @return ActivityLogger
*/
public function getActivityLogger()
{
return $this->activityLogger;
}
/**
* @return Listing
*/
public function createDraft(Listing $listing)
{
$listing->setStatus(ListingStatus::DRAFT);
$this->saveListing($listing);
return $listing;
}
/**
* @return Listing
*/
public function changeLocation(Listing $listing, Location $location)
{
$listing->setLocation($location);
$this->saveListing($listing);
return $listing;
}
/**
* @return Listing
*/
public function changePropertyType(Listing $listing, PropertyType $propertyType)
{
$listing->setPropertyType($propertyType);
$this->saveListing($listing);
return $listing;
}
/**
* @return Listing
*
* @internal param PropertyType $propertyType
*/
public function changeSection(Listing $listing, Section $section)
{
// Getting listing rules and matching it with listing, and check if the listing can be Featured or not
$listingRule = $this->getMatcher()->match($listing);
$listingOwner = $listing->getUser();
$availableBalance = $this->getCreditManager()->getBalance($listingOwner);
$sectionChangePeriod = $this->settings->getSetting('general', 'change_section_period');
if ($listing->getPublishedAt() && $listing->getPublishedAt() < new \DateTime('-'.$sectionChangePeriod.' days')) {
throw new \RuntimeException('This listing published more than '.$sectionChangePeriod.' days ago, can not change section');
}
$listingFees = 0;
$listingBalance = 0;
foreach ($listing->getCurrentListingFeatures() as $listingFeature) {
$listingFeatureCredit = $listingFeature->getCredit();
$listingBalance += abs($listingFeatureCredit->getAmount());
$listingFees += $listingRule[ListingFeesTypes::getLabel($listingFeature->getType())];
}
// If the listing owner doesn't have enough credits
if ($listingFees > ($availableBalance + $listingBalance)) {
throw new \RuntimeException('There\'s no enough credits to make this listing featured.');
}
foreach ($listing->getCurrentListingFeatures() as $listingFeature) {
$this->cancelAllFeatures($listingFeature);
}
foreach ($listing->getCurrentListingFeatures() as $listingFeature) {
$this->ReCreateFeatures($listingFeature);
}
$listing->setSection($section);
$this->saveListing($listing);
return $listing;
}
/**
* cancel Features for listing.
*
* @param ListingFeature $listingFeature
*/
public function cancelAllFeatures($listingFeature): void
{
$listingFeature->getCredit()->setStatus(CreditStatus::CANCELLED);
$listingFeature->getCredit()->setDescription($listingFeature->getCredit()->getDescription());
$this->em->persist($listingFeature->getCredit());
$this->em->flush($listingFeature->getCredit());
}
/**
* Recreate Features for listing.
*
* @param ListingFeature $listingFeature
*/
public function ReCreateFeatures($listingFeature): void
{
$listingRule = $this->getMatcher()->match($listingFeature->getListing());
$newCredits = $this->getCreditManager()->deduction(
$listingFeature->getListing()->getUser(),
$listingRule[ListingFeesTypes::getLabel($listingFeature->getType())],
CreditDescriptionTypes::getLabel($listingFeature->getType()),
CreditStatus::SUCCESS,
false
);
foreach ($newCredits as $newCredit) {
$this->em->persist($newCredit);
$this->em->flush($newCredit);
$this->addFeature($listingFeature->getListing(), $listingFeature->getType(), $listingFeature->getExpiresAt(), $newCredit);
$listingFeature->setExpiresAt(new \DateTime());
$this->em->persist($listingFeature);
$this->em->flush($listingFeature);
}
}
/**
* Change listing area.
*
* @return Listing
*/
public function changeArea(Listing $listing, $area)
{
$listing->setArea($area);
$this->saveListing($listing);
return $listing;
}
/**
* Change listing payment method.
*
* @return Listing
*/
public function changePaymentMethod(Listing $listing, $paymentMethod)
{
$listing->setPaymentMethod($paymentMethod);
$this->saveListing($listing);
return $listing;
}
/**
* @param bool $flush
*
* @throws ORMException
* @throws OptimisticLockException
*/
public function saveListing(Listing $listing, $flush = true, $category = null): void
{
$this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.pre_save');
$listing->setPricePerMeter($listing->calculatePricePerMeter() ?? null);
$listing->generateCoordinates();
$listing = $this->handleListingFeatureOnSave($listing);
$this->em->persist($listing);
false === $flush ?: $this->em->flush();
$this->setProjectUnitsOwner($listing);
$this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.after_save');
if ($listing->getParent()) {
$this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.project.re_calculate.min_price_area');
$this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.project.calculate.average.pricePerMeter');
}
if (UserTypes::BUYER === $listing->getUser()->getUserType()) {
$this->getUserManagerService()->changeUserType($listing->getUser(), UserTypes::INDIVIDUAL);
}
}
/**
* Change listing status.
*
* @param bool $flush
* @param bool $force
*/
public function changeStatus(Listing $listing, $status, $flush = true, $force = false, array $rejectionReasons = []): void
{
$token = $this->tokenStorage->getToken();
$isAdmin = $token && ($token->getUser()->hasRole('ROLE_ADMIN') || $token->getUser()->hasRole('ROLE_SUPER_ADMIN'));
if (!$force && $listing->getStatus() === $status) {
return;
}
$this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.pre_change_status');
if (
ListingStatus::LIVE == $listing->getStatus()
&& ListingFeaturedTypes::NOT_FEATURED != $listing->getFeatured()
) {
$this->pauseFeatured($listing);
} elseif (
$status
&& ListingStatus::LIVE == $status
&& ListingFeaturedTypes::NOT_FEATURED != $listing->getFeatured()
) {
$this->unPauseFeatured($listing);
}
if (!\in_array($status, [ListingStatus::ADMIN_DELETED, ListingStatus::USER_DELETED])) {
$this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.undelete');
}
switch (true) {
// If pending/draft/rejected/expired == to ==> live
case ListingStatus::LIVE == $status && \in_array($listing->getStatus(), [
ListingStatus::PENDING,
ListingStatus::DRAFT,
ListingStatus::REJECTED,
ListingStatus::EXPIRED,
]):
$this->dispatcher->dispatch((new ListingWasPublished())->setSubject($listing)->prepareData(), 'listing.was.published');
$this->dispatcher->dispatch(new ListingNotificationEvent($listing), 'aqarmap.listing.publish');
break;
case ListingStatus::EXPIRED == $status:
$this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.expired');
break;
case ListingStatus::REJECTED == $status:
$listingWasRejectedEvent = (new ListingWasRejected())->setSubject($listing);
$this->dispatcher->dispatch($listingWasRejectedEvent, 'listing.was.rejected');
$this->dispatcher->dispatch(new ListingEvent($listing, $rejectionReasons), 'aqarmap.listing.rejected');
$this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing_got_rejected');
break;
case ListingStatus::DRAFT == $listing->getStatus():
$this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.created');
break;
case ListingStatus::PENDING_PHOTOS == $status:
$this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.pending_photos');
break;
case ListingStatus::PENDING_PAYMENT == $status:
$this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.pending_payment');
break;
case ListingStatus::LIVE == $status && ListingStatus::PENDING_PAYMENT == $listing->getStatus():
$this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.free_publish');
break;
case ListingStatus::LIVE == $status && ListingStatus::PENDING_PHOTOS == $listing->getStatus():
$this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.publish_without_photos');
break;
case ListingStatus::ADMIN_DELETED == $status:
$this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.delete');
break;
case ListingStatus::USER_DELETED == $status:
$this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.delete_by_user');
break;
case ListingStatus::PENDING == $status:
$this->dispatcher->dispatch(new ListingEvent($listing, [], $isAdmin), 'aqarmap.listing.pending_approval');
break;
}
$listing->setStatus($status);
$listing->setUpdatedAt(new \DateTime());
if (ListingCategories::PROJECTS == $listing->getCategory()) {
$this->handleProjectDates($listing);
}
$this->em->persist($listing);
false === $flush ?: $this->em->flush();
$this->logListingIfProject($listing);
if ($this->featureToggleManager->isEnabled('web.events_streaming') && $listing->getId()) {
$this->dispatchListingStatusChangeEvent($listing);
}
$this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.after_change_status');
}
/**
* Dispatch Listing Status Change Event.
*
*@param Listing
*/
private function dispatchListingStatusChangeEvent(Listing $listing): void
{
$this->eventStreamingFactory->create(StreamingEventTopic::LISTING_STATUS_CHANGED)->publish(serialize([
'type' => StreamingEventTopic::LISTING_STATUS_CHANGED,
'payload' => [
'id' => $listing->getId(),
'location' => $listing->getLocation() ? $listing->getLocation()->getId() : null,
'propertyType' => $listing->getPropertyType()->getId(),
'section' => $listing->getSection()->getId(),
'user' => $listing->getUser()->getId(),
],
]));
}
public function changePendingPaymentStatus(Listing $listing, $status): void
{
$listing->setPendingPaymentStatus($status);
$this->em->persist($listing);
$this->em->flush();
}
public function changePendingPhotosStatus(Listing $listing, $status): void
{
$listing->setPendingPhotosStatus($status);
$this->em->persist($listing);
$this->em->flush();
}
public function changeRelistStatus(Listing $listing, $status): void
{
$listing->setRelistStatus($status);
$this->em->persist($listing);
$this->em->flush();
}
public function remove(Listing $listing, $status): void
{
$this->changeStatus($listing, $status, false);
$listing->setDeletedAt(new \DateTime());
$location = $listing->getLocation();
$locationListingId = $location->getListing() ? $location->getListing()->getId() : null;
if ($location && $locationListingId == $listing->getId()) {
$location->setListing(null);
$this->em->persist($location);
}
$this->em->persist($listing);
$this->em->flush();
}
public function setMainPhoto(ListingPhoto $listingPhoto): void
{
// If there's already a main photo swap orders
if ($currentMainPhoto = $listingPhoto->getListing()->getMainPhoto()) {
if (PhotoTypes::MAIN_PHOTO == $currentMainPhoto->getType()) {
$this->swapListingPhotos($listingPhoto, $listingPhoto->getListing()->getMainPhoto());
$this->em->persist($currentMainPhoto);
}
}
$this->changePhotoType($listingPhoto, PhotoTypes::MAIN_PHOTO);
}
/**
* @param bool $flush
*/
public function swapListingPhotos(ListingPhoto $firstListingPhoto, ListingPhoto $secondListingPhoto, $flush = false): void
{
// Swapping Orders
$tempOrder = $firstListingPhoto->getOrder();
$firstListingPhoto->setOrder($secondListingPhoto->getOrder());
$secondListingPhoto->setOrder($tempOrder);
$this->em->persist($firstListingPhoto);
$this->em->persist($secondListingPhoto);
if ($flush) {
$this->em->flush();
}
}
/**
* @param ListingPhoto $listingPhoto |null
* @param bool $flush
*/
public function changePhotoType(?ListingPhoto $listingPhoto, $type, $flush = true): void
{
if (!$listingPhoto) {
return;
}
if ($this->isValidPhotoType($type)) {
foreach ($listingPhoto->getListing()->getPhotos() as $iterablePhoto) {
if ($iterablePhoto->getType() == $type) {
$iterablePhoto->setType(null);
}
}
$listingPhoto->setType($type);
if ($listingPhoto->getListing()->getChildren() && PhotoTypes::LOGO_PHOTO == $type) {
$this->setLogoPhoto($listingPhoto);
}
$this->em->persist($listingPhoto);
if ($flush) {
$this->em->flush();
}
}
}
public function setLogoPhoto($listingPhoto): void
{
foreach ($listingPhoto->getListing()->getChildren() as $child) {
$listingPhotosToBeLogo = $this->listingPhotoRepository->findOneBy([
'listing' => $child,
'file' => $listingPhoto->getFile(),
]);
try {
$this->changePhotoType($listingPhotosToBeLogo, PhotoTypes::LOGO_PHOTO, false);
} catch (\Exception $exception) {
$this->logger->error($exception->getMessage().' '.$child->getId());
}
}
}
/**
* Reset listing description numbers.
*/
public function removeNumbers(Listing $listing): void
{
$description = preg_replace('/(\d{7,})/u', '', $listing->getDescription());
$this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.remove_description_numbers');
$listing->setDescription($description);
$this->em->flush();
foreach ($this->locales as $locale) {
$this->translatableListener->setTranslatableLocale($locale);
$listing->setTranslatableLocale($locale);
$this->em->refresh($listing);
$description = preg_replace('/(\d{7,})/u', '', $listing->getDescription());
$listing->setDescription($description);
$this->em->flush();
}
}
public function addFeature(Listing $listing, int $type, ?\DateTime $expiresAt, ?Credit $credit = null): void
{
$currentDate = new \DateTime();
$expiresAt = ListingCategories::PROJECTS == $listing->getCategory() ? $currentDate->modify(self::ADD_TEN_YEARS) : $expiresAt;
$listingFeature = new ListingFeature();
$listingFeature
->setType($type)
->setCreatedAt(new \DateTime())
->setListing($listing)
->setCredit($credit)
->setExpiresAt($expiresAt)
->setLeadsCountSnapshot($listing->getLeadsCounter());
if ($this->requiresFeaturingReview($this->listingFeatureService->getListingFeaturedType($type))) {
$listingFeature->setFeaturingStatus(ListingFeaturedStatus::PENDING);
}
$this->em->persist($listingFeature);
$listing->addListingFeature($listingFeature);
if ($expiresAt && $listing->getExpiresAt() < $listingFeature->getExpiresAt()) {
$listing->setExpiresAt($expiresAt);
}
$this->saveListing($listing);
$this->dispatcher->dispatch(new ListingFeatureEvent($listingFeature), 'aqarmap.listing.feature.bumpup');
}
/**
* @return \Doctrine\Common\Collections\Collection
*/
public function getSimilarListings(Listing $listing)
{
$criteria = [
'minArea' => $listing->getArea() * 0.65,
'maxArea' => $listing->getArea() * 1.35,
'location' => $listing->getLocation(),
'propertyType' => $listing->getPropertyType(),
'section' => $listing->getSection(),
'minPrice' => $listing->getPrice() * 0.65,
'maxPrice' => $listing->getPrice() * 1.35,
'exclude' => [$listing->getId()],
'limit' => 3,
];
$similarListings = $this->listingRepository->search($criteria)->getQuery()->getResult();
if (\count($similarListings) < 3 && $listing->getLocation()->getParent()) {
$criteria['location'] = $listing->getLocation()->getParent();
$criteria = [
'minArea' => $listing->getArea() * 0.75,
'maxArea' => $listing->getArea() * 1.25,
'location' => $listing->getLocation()->getParent(),
'minPrice' => $listing->getPrice() * 0.75,
'maxPrice' => $listing->getPrice() * 1.25,
];
$similarListings = $this->listingRepository->search($criteria)->getQuery()->getResult();
}
return $similarListings;
}
public function updateListingLeadsCounter(Listing $listing, $flush = true): void
{
$leadRepository = $this->entityManager->getRepository(Lead::class);
$totalLeads = $leadRepository->countLeadsByListing($listing);
$listing
->setPhoneCounter($totalLeads)
->setLeadsCounter($totalLeads)
;
$this->em->persist($listing);
if ($flush) {
$this->em->flush();
}
}
public function updateListingInteractionsCounter(Listing $listing, $interactionType, $flush = true): void
{
$interactionTypes = [$interactionType];
$count = $this->interactionRepository->getListingInteractionCount(
$interactionTypes,
$listing
);
switch ($interactionType) {
case InteractionTypes::LISTING_VIEWS:
$listing->setViews($count);
break;
default:
break;
}
$this->em->persist($listing);
if ($flush) {
$this->em->flush();
}
}
public function addParticipant(Listing $listing, User $user): void
{
$listing->addParticipant($user);
$this->em->persist($listing);
$this->em->flush();
}
/**
* @param array $status
* @param array $searchCriteria
* @param bool $exclude
* @param bool $mergeDraftAndDeleted
*/
public function getUserListings(User $user, $status = [], $searchCriteria = [], $exclude = false, $mergeDraftAndDeleted = true)
{
if ($exclude && $mergeDraftAndDeleted) {
$draftAndDeleted = [
ListingStatus::DRAFT,
ListingStatus::USER_DELETED,
ListingStatus::ADMIN_DELETED,
];
$status = array_merge($draftAndDeleted, $status);
}
if (!empty($searchCriteria['location'])) {
$location = $this->locationRepository->find($searchCriteria['location']);
$searchCriteria['location'] = $this->locationRepository->getLocationChildren($location);
}
$locale = $this->requestStack->getCurrentRequest()->getLocale();
return $this->listingRepository->getListingsForMyListingsPage(array_merge(['user' => $user, 'status' => (array) $status, 'exclude' => $exclude], $searchCriteria), $locale);
}
/**
* @param array $status
* @param array $searchCriteria
* @param bool $exclude
* @param bool $mergeDraftAndDeleted
*/
public function getUserListingsQuery(User $user, $status = [], $searchCriteria = [], $exclude = false, $mergeDraftAndDeleted = true)
{
if ($exclude && $mergeDraftAndDeleted) {
$draftAndDeleted = [
ListingStatus::DRAFT,
ListingStatus::USER_DELETED,
ListingStatus::ADMIN_DELETED,
];
$status = array_merge($draftAndDeleted, $status);
}
if (!empty($searchCriteria['location'])) {
$location = $this->em->getReference('AqarmapListingBundle:Location', $searchCriteria['location']);
$searchCriteria['location'] = $this->em->getRepository(Location::class)->getLocationChildren($location);
}
$locale = $this->requestStack->getCurrentRequest()->getLocale();
return $this->em->getRepository(Listing::class)->getListingsForMyListingsPageQuery(array_merge(['user' => $user, 'status' => $status, 'exclude' => $exclude], $searchCriteria), $locale);
}
/**
* Make a listing featured, and deduct fees.
*
* @return bool
*
* @throws \Exception
*/
public function makeItFeatured(Listing $listing, array $rules)
{
$expiredAt = null;
$featuredFees = $rules['featuredFees'];
$listingFeaturedType = $rules['listingFeaturedType'];
$featuredDuration = $rules['featuredDuration'];
$listingFeature = $rules['listingFeature'];
// Check if the listing has a valid credit transaction for
// making a listing featured to avoid double charging
if ($this->isFeatured($listing)) {
throw new \RuntimeException($this->translator->trans('listing.featured_failure_statement.already_featured', ['%listingId%' => $listing->getId()]));
}
if (empty($featuredFees) || empty($featuredDuration)) {
throw new \RuntimeException($this->translator->trans('listing.featured_failure_statement.not_available'));
}
$listingOwner = $listing->getUser();
$availableBalance = $this->getCreditManager()->getBalance($listingOwner);
if ($featuredFees > $availableBalance) {
throw new \RuntimeException($this->translator->trans('listing.featured_failure_statement.no_enough_credit'));
}
$requiresFeaturingReview = $this->requiresFeaturingReview($listingFeaturedType);
// Let's take our money :)
$credits = $this->getCreditManager()->deduction(
$listingOwner,
$featuredFees,
$this->translator->trans('credit.deduction.featured', [':featured_type:' => $rules['featuredText']]),
true
);
foreach ($credits as $credit) {
if (!$credit instanceof Credit) {
throw new \RuntimeException($this->translator->trans('credit.operation_failed'));
}
if (ListingStatus::LIVE == $listing->getStatus() && !$requiresFeaturingReview) {
$expiredAt = new \DateTime('+'.$featuredDuration.' days');
}
$this->addFeature($listing, $listingFeature, $expiredAt, $credit);
}
if (!$requiresFeaturingReview) {
$listing->setFeatured($listingFeaturedType);
}
$listing->setIsTopPicks(false);
$this->saveListing($listing, true);
return true;
}
public function handleDepthProductServices(Listing $listing, Service $service): void
{
$ruleLabel = ListingFeaturedTypes::getRuleLabel($service->getFeaturingType());
$rules = $this->listingFeatureService->getFeaturedListingRules($listing, [$ruleLabel]);
$rules = [
'featuredFees' => $rules[$ruleLabel]['fees'],
'featuredDuration' => $rules[$ruleLabel]['duration'],
'listingFeaturedType' => $rules[$ruleLabel]['listingFeaturedType'],
'listingFeature' => $rules[$ruleLabel]['listingFeature'],
'featuredText' => ListingFeaturedTypes::getFeaturedText($service->getFeaturingType()),
];
try {
$this->makeItFeatured($listing, $rules);
} catch (\Exception $exception) {
}
}
/**
* Pump up listing.
*
* @return bool
*
* @throws \Exception
*/
public function pumpUp(Listing $listing)
{
if ($listing->getLastAutoBumpUp()->isBumpable()) {
throw new \RuntimeException('This listing has already been pumped up.');
}
$listingRules = $this->getMatcher()->match($listing);
$pumpUpFees = $listingRules['pump_up_fees'];
$pumpUpOccurrence = $listingRules['pump_up_occurrence'];
$pumpUpDuration = $listingRules['pump_up_duration'];
$listingOwner = $listing->getUser();
$credits = $this->getCreditManager()->deduction(
$listingOwner,
$pumpUpFees,
'Fees for making a listing pump up',
true
);
foreach ($credits as $credit) {
if (!$credit instanceof Credit) {
throw new \RuntimeException($this->translator->trans('credit.operation_failed'));
}
$credit->setStatus(CreditStatus::SUCCESS);
$listing->setBumped(true);
$pumpUpService = $this->listingFeatureService;
$pumpUpModel = $this->pumpUpModel;
$pumpUpModel
->setListing($listing)
->setDuration($pumpUpDuration)
->setOccurrences($pumpUpOccurrence)
->setOwner($listingOwner)
->setCredit($credit);
$pumpUpService->createBumpUp($pumpUpModel);
}
$this->saveListing($listing, true);
$activityLogger = $this->activityLogger;
$activityLogger->record(ActivityType::LISTING_BUMP_UP, $listing->getId());
$pumpUpService->logBumpActivity('aqarmap.listing.bump_up_started', $listing, $listingOwner);
return true;
}
/**
* Check if the listing has a valid transaction for making a listing Featured.
*
* @return bool
*
* @throws \Exception
*/
public function isFeatured(Listing $listing, $type = null)
{
if ($listing->getFeaturedPublicationCredit($type)) {
return true;
}
return false;
}
/**
* @return Listing
*/
public function relist(Listing $listing, array $criteria = [])
{
$isAdminRelist = isset($criteria['isAdminRelist']) && true == $criteria['isAdminRelist'];
// Clone and set relist parent.
$clonedListing = $this->cloneListing($listing);
if ($isAdminRelist) {
$clonedListing->setPublishedAt(new \DateTime());
}
if (ListingCategories::UNLIMITED == $clonedListing->getCategory()) {
$clonedListing = $this->handleUnlimitedListingOnRelist($clonedListing);
}
$clonedListing->setRelistParent($listing);
if (isset($criteria['price']) && is_numeric($criteria['price'])) {
// Setting the new price to the cloned listing.
$clonedListing->setPrice($criteria['price']);
}
if ($isAdminRelist) {
$this->changeStatus($clonedListing, ListingStatus::LIVE, true);
} else {
$this->changeStatus($clonedListing, ListingStatus::PENDING, true);
}
$this->saveListing($clonedListing, false);
// Removing this relisted listing from the section and avoid catching it again
$listing->setRelistStatus(ListingPendingStatus::CHECKED);
$this->changeStatus($listing, ListingStatus::ADMIN_DELETED, false);
if ($isAdminRelist) {
$this->remove($listing, ListingStatus::ADMIN_DELETED);
}
$this->em->persist($listing);
$this->em->flush();
return $clonedListing;
}
/**
* @return Listing
*/
public function cloneListing(Listing $listing)
{
$clonedListing = new Listing();
$clonedListing->setStatus($listing->getStatus());
$clonedListing->setAddress($listing->getAddress());
$clonedListing->setArea($listing->getArea());
$clonedListing->setCategory($listing->getCategory());
$clonedListing->setCenterLat($listing->getCenterLat());
$clonedListing->setCenterLng($listing->getCenterLng());
$clonedListing->setDescription($listing->getDescription());
$clonedListing->setIp($listing->getIp());
$clonedListing->setLocation($listing->getLocation());
$clonedListing->setPrice($listing->getPrice());
$clonedListing->setPropertyType($listing->getPropertyType());
$clonedListing->setSection($listing->getSection());
$clonedListing->setSellerRole($listing->getSellerRole());
$clonedListing->setTitle($listing->getTitle());
$clonedListing->setUser($listing->getUser());
$clonedListing->setVideoUrl($listing->getVideoUrl());
// cloning photos
$photosClone = new ArrayCollection();
foreach ($listing->getPhotos() as $item) {
$itemClone = clone $item;
$photosClone->add($itemClone);
}
$clonedListing->setPhotos($photosClone);
// cloning attributes
$attributesClone = new ArrayCollection();
foreach ($listing->getAttributes() as $item) {
$itemClone = clone $item;
$attributesClone->add($itemClone);
}
$clonedListing->setAttributes($attributesClone);
// cloning phones
$phonesClone = new ArrayCollection();
foreach ($listing->getPhones() as $item) {
$itemClone = clone $item;
$phonesClone->add($itemClone);
}
$clonedListing->setPhones($phonesClone);
// cloning locations
$locationClone = new ArrayCollection();
foreach ($listing->getLocations() as $item) {
$itemClone = clone $item;
$locationClone->add($itemClone);
}
$clonedListing->setLocations($locationClone);
// cloning translations
$translationClone = new ArrayCollection();
foreach ($listing->getTranslations() as $item) {
$itemClone = clone $item;
$translationClone->add($itemClone);
}
$clonedListing->setTranslations($translationClone);
return $clonedListing;
}
/**
* Check if listing is live for number of days(5 days).
*/
public function checkListingLiveDays(Listing $listing, int $days = 5): bool
{
if (null != $listing->getPublishedAt() && ListingStatus::LIVE == $listing->getStatus()) {
$daysAgo = new \DateTime(sprintf('-%d Days', $days));
return $listing->getPublishedAt() < $daysAgo;
}
return false;
}
/**
* @return CreditManager
*/
public function getCreditManager()
{
return $this->creditManager;
}
/**
* @return ListingRuleMatcher
*/
public function getMatcher()
{
return $this->matcher;
}
// --------------------------------------------------------------------------
/**
* @param LeadModel $leadModel
*
* @return CallRequest|Composer|bool|object
*/
public function quickCreateLead($leadModel)
{
if (null != $leadModel->getMessage() || 'Home.eg-New-Homes' == $leadModel->getCampaign()) {
// Send contact seller message and send a lead
$composer = $this->messageComposer;
$composer
->setSender($leadModel->getUser())
->compose($leadModel->getMessage(), $leadModel->getListing(), $leadModel->getCampaign());
} else {
// Create Call Request
$callRequest = new CallRequest();
$callRequest->setUser($leadModel->getUser());
$callRequest->setListing($leadModel->getListing());
$callRequest->setLeadFullName($leadModel->getName());
$callRequest->setLeadEmail($leadModel->getEmail());
$callRequest->setPhone($leadModel->getPhone());
$this->callRequestManager->submitCallRequest($callRequest, $leadModel->getCampaign());
}
return true;
}
// --------------------------------------------------------------------------
/**
* Get listings IDs from SlidingPagination.
*
* @return array
*/
public function getIdsFromPagination(SlidingPagination $listings)
{
$listingsId = [];
foreach ($listings as $listing) {
$listingsId[] = $listing->getId();
}
return $listingsId;
}
/**
* @return int
*/
public function getMaxListingPhotoOrder(Listing $listing)
{
$lastPhoto = $this->listingPhotoRepository->findOneBy(['listing' => $listing], ['order' => 'DESC']);
if ($lastPhoto) {
return $lastPhoto->getOrder();
}
return 0;
}
public function syncListing(Listing $targetListing, Listing $sourceListing, $syncedFields, $arguments = []): void
{
if (\in_array('photos', $syncedFields)) {
$listingPhotos = $sourceListing->getPhotos();
if (isset($arguments['photos'])) {
$listingPhotos = $arguments['photos'];
}
$this->syncPhotos($targetListing, $sourceListing, $listingPhotos);
}
if (\in_array('translations', $syncedFields)) {
if (isset($arguments['monolingual'])) {
$targetListing->setAddress($sourceListing->getAddress());
$targetListing->setDescription($sourceListing->getDescription());
$this->em->persist($targetListing);
$this->em->flush($targetListing);
} else {
foreach ($this->locales as $locale) {
$this->translatableListener->setTranslatableLocale($locale);
$this->em->refresh($sourceListing);
$targetListing->setAddress($sourceListing->getAddress());
$targetListing->setDescription($sourceListing->getDescription());
$this->em->persist($targetListing);
$this->em->flush($targetListing);
}
}
}
if (\in_array('attributes', $syncedFields)) {
// Only Sync attributes if the property types are equal
if ($sourceListing->getPropertyType() == $targetListing->getPropertyType()) {
/** @var ListingAttribute $attribute */
foreach ($sourceListing->getAttributes() as $attribute) {
$targetAttribute = new ListingAttribute();
$targetAttribute->setCustomField($attribute->getCustomField());
$targetAttribute->setValue($attribute->getValue());
$targetListing->addAttribute($targetAttribute);
}
}
}
if (\in_array('phoneNumbers', $syncedFields)) {
$listingPhones = $sourceListing->getPhones();
if (isset($arguments['phoneNumbers'])) {
$listingPhones = $arguments['phoneNumbers'];
}
/* @var ListingPhone $phone */
$this->syncPhones($targetListing, $sourceListing, $listingPhones);
}
if (\in_array('participants', $syncedFields)) {
$participants = $sourceListing->getParticipants();
if (isset($arguments['participants'])) {
$participants = $arguments['participants'];
}
$this->syncParticipants($targetListing, $sourceListing, $participants);
}
$this->saveListing($targetListing);
}
/**
* @throws ORMException
* @throws OptimisticLockException
*/
public function syncListingForProject(array $criteria): bool
{
$targetListingId = $criteria['targetListingId'];
$sourceListingId = $criteria['sourceListingId'];
$syncedFields = $criteria['syncedFields'];
$targetListingSavedAt = $criteria['targetListingSavedAt'] ?? null;
$sourceListing = $this->listingRepository->findOneBy(['id' => $sourceListingId]);
$targetListing = $this->listingRepository->findOneBy(['id' => $targetListingId]);
if (!$this->isReadyForSyncListingForProject($sourceListing, $targetListing, $targetListingSavedAt)) {
return false;
}
if (\in_array('photos', $syncedFields)) {
$listingPhotos = $sourceListing->getPhotos();
$this->syncPhotos($targetListing, $sourceListing, $listingPhotos);
}
if (\in_array('translations', $syncedFields)) {
foreach ($this->locales as $locale) {
$this->translatableListener->setTranslatableLocale($locale);
$this->em->refresh($sourceListing);
$targetListing->setAddress($sourceListing->getAddress());
$targetListing->setDescription($sourceListing->getDescription());
$this->em->persist($targetListing);
$this->em->flush();
}
}
if (\in_array('attributes', $syncedFields)) {
// Only Sync attributes if the property types are equal
if ($sourceListing->getPropertyType() == $targetListing->getPropertyType()) {
$targetListing = $this->cloneAttributes($sourceListing, $targetListing);
}
}
if (\in_array('phoneNumbers', $syncedFields)) {
$listingPhones = $sourceListing->getPhones();
/* @var ListingPhone $phone */
$this->syncPhones($targetListing, $sourceListing, $listingPhones);
}
if (\in_array('participants', $syncedFields)) {
$participants = $sourceListing->getParticipants();
$this->syncParticipants($targetListing, $sourceListing, $participants);
}
$this->saveListing($targetListing);
return true;
}
public function filterListingPhones(Listing $listing): void
{
$listingPhones = $listing->getPhones()->toArray();
$listing->removePhones();
$filteredPhones = $this->phoneManager->addListingPhonesList($listingPhones, '', $listing);
$listing->setPhones($filteredPhones);
}
/**
* @return array
*/
public function suggestListingPhoneNumbers($userPhones, Listing $listing)
{
$listingPhones = [];
foreach ($userPhones as $phone) {
$this->phoneManager->addNewListingPhone(
$phone,
'',
$listing,
true
);
$listingPhones[] = $phone;
}
return $listingPhones;
}
public function savePhotos(array $photos, Listing $listing): void
{
$listingPhotos = [];
$maxOrder = $this->getMaxListingPhotoOrder($listing);
foreach ($photos as $index => $file) {
$photo = new Photo();
$photo->setFile($file);
$listingPhoto = new ListingPhoto();
$listingPhoto->setFile($photo);
$listingPhoto->setCaption($photo->getFile()->getClientOriginalName());
$listingPhoto->setOrder($maxOrder + $index + 1);
$listingPhotos[] = $listingPhoto;
$listing->addPhoto($listingPhoto);
}
$this->saveListing($listing);
}
/**
* Sync locations.
*/
public function syncLocations(Listing $parentListing, Location $location, bool $flush = false): void
{
$batchSize = 20;
$loopIndex = 1;
foreach ($parentListing->getChildren() as $childListing) {
if ($parentListing->hasLocation($location) && !$childListing->hasLocation($location)) {
$childListing->addLocation($location);
} elseif (!$parentListing->hasLocation($location) && $childListing->hasLocation($location)) {
$childListing->removeLocation($location);
}
$this->em->persist($childListing);
if (($loopIndex % $batchSize) === 0) {
$this->em->flush();
$this->em->clear();
}
++$loopIndex;
}
if ($flush) {
$this->em->flush();
$this->em->clear();
}
}
/**
* Sync slug.
*/
public function syncSlug(Listing $parentListing): void
{
$this->listingRepository->updateChildrenSlug($parentListing);
}
/**
* Sync video URL.
*/
public function syncVideoUrl(Listing $parentListing): void
{
$this->listingRepository->updateChildrenVideoUrl($parentListing);
}
public function syncPhotos(Listing $targetListing, Listing $sourceListing, $listingPhotos): void
{
$maxOrder = $this->getMaxListingPhotoOrder($targetListing);
$index = 1;
/** @var ListingPhoto $listingPhoto */
foreach ($listingPhotos as $listingPhoto) {
$listingPhotoRepo = $this->em->getRepository(ListingPhoto::class);
$sourceListingPhoto = $listingPhotoRepo->findOneBy([
'file' => $listingPhoto->getFile(),
'listing' => $sourceListing,
]);
$targetListingPhoto = $listingPhotoRepo->findOneBy([
'file' => $listingPhoto->getFile(),
'listing' => $targetListing,
]);
if ($sourceListingPhoto && !$targetListingPhoto) {
$targetPhoto = new ListingPhoto();
$targetPhoto->setFile($listingPhoto->getFile());
$targetPhoto->setCaption($listingPhoto->getCaption());
$targetPhoto->setOrder($maxOrder + $index++);
$targetPhoto->setType($listingPhoto->getType());
$targetListing->addPhoto($targetPhoto);
} elseif (!$sourceListingPhoto && $targetListingPhoto) {
$targetListing->removePhoto($targetListingPhoto);
}
}
}
/**
* Update Listing Translations.
*/
public function updateListingTranslations(Listing $listing): void
{
$currentLocale = $this->requestStack->getCurrentRequest()->getLocale();
$reversedLocale = null;
// Identifying Submitted Locale
foreach ($this->locales as $locale) {
if ($currentLocale != $locale) {
$reversedLocale = $locale;
}
}
$currentTitle = $listing->getTitle();
$listing->setTranslatableLocale($reversedLocale);
$this->em->refresh($listing);
$translatedTitle = $listing->getTitle();
if (null != $reversedLocale && (null == $listing->getTitle() || $currentTitle == $translatedTitle)) {
$title = $this->listingTitleTranslations($listing, $reversedLocale);
$description = $this->listingDescriptionTranslations($listing, $reversedLocale);
$address = $this->listingAddressTranslations($listing, $reversedLocale);
$listing->setTitle($title);
$listing->setDescription($description);
$listing->setAddress($address);
$listing->setTranslatableLocale($reversedLocale);
$this->em->persist($listing);
$this->em->flush();
}
}
/**
* Update Listing Translations Fields.
*/
public function updateListingTranslationsFields(Listing $listing, array $fields): void
{
if ('ar' == $fields['reversedLocale']) {
$listing->setDescription($fields['description-ar']);
$listing->setTitle($fields['title-ar']);
$address = $this->listingAddressTranslations($listing, $fields['reversedLocale']);
} else {
$listing->setDescription($fields['description-en']);
$listing->setTitle($fields['title-en']);
$address = $this->listingAddressTranslations($listing, $fields['reversedLocale']);
}
$listing->setAddress($address);
$listing->setTranslatableLocale($fields['reversedLocale']);
$this->em->persist($listing);
$this->em->flush();
}
/**
* Generate auto-translation to listing title.
*
* @param null $locale
*/
public function listingTitleTranslations(Listing $listing, $locale = null)
{
if (null == $locale) {
$locale = $this->requestStack->getCurrentRequest()->getLocale();
}
$this->translatableListener->setTranslatableLocale($locale);
$this->em->refresh($listing->getLocation());
$this->em->refresh($listing->getPropertyType());
$this->em->refresh($listing->getSection());
return $this->generateTranslatedListingTitle($listing, $locale);
}
/**
* Generate auto-translation to listing title.
*
* @param null $locale
*/
public function unitTitleTranslations(Listing $listing, Listing $sourceListing, $locale = null)
{
if (null == $locale) {
$locale = $this->requestStack->getCurrentRequest()->getLocale();
}
$this->translatableListener->setTranslatableLocale($locale);
$this->em->refresh($sourceListing);
$this->em->refresh($listing->getLocation());
$this->em->refresh($listing->getPropertyType());
$this->em->refresh($listing->getSection());
return $this->generateTranslatedUnitTitle($listing, $sourceListing, $locale);
}
/**
* Generate auto-translation to listing description.
*
* @param null $locale
*/
public function listingDescriptionTranslations(Listing $listing, $locale = null)
{
if (null == $locale) {
$locale = $this->requestStack->getCurrentRequest()->getLocale();
}
$this->translatableListener->setTranslatableLocale($locale);
$this->em->refresh($listing->getLocation());
$this->em->refresh($listing->getPropertyType());
$this->em->refresh($listing->getSection());
$listingDescriptions = [];
$context = $this->router->getContext();
$context->setParameter('_locale', $locale);
$sizeUnit = $this->settings->getSetting('general', 'measurement_unit');
$view = $listing->getPropertyView()
? $this->translator->trans('layout.property_view', [], null, $locale).' '.$this->translator->trans($listing->getPropertyViewLabel(), [], null, $locale)
: '';
if (
$listing->getPropertyType()
&& $listing->getAttribute('finish-type')
&& $listing->getArea()
&& $listing->getAttribute('floor')
&& $listing->getAttribute('rooms')
&& $listing->getAttribute('baths')
) {
$listingDescriptions[] = $this->translator->trans('layout.description.translations.property_type_size_finish_floor_view_room_bath', [
'%property_type%' => $listing->getPropertyType()->getTitle(),
'%size%' => $listing->getArea().' '.$this->translator->trans($sizeUnit, [], null, $locale),
'%finish%' => $this->translator->trans($listing->getAttribute('finish-type')->getValue(), [], null, $locale),
'%view%' => $view,
'%floor%' => $this->numberToWordService->convertToRank($listing->getAttribute('floor')->getValue(), $locale),
'%rooms%' => $this->numberToWordService->convertToNum($listing->getAttribute('rooms')->getValue(), $locale),
'%baths%' => $this->numberToWordService->convertToNum($listing->getAttribute('baths')->getValue(), $locale),
], null, $locale);
}
if (
$listing->getSection()
&& $listing->getPropertyType()
&& $listing->getAttribute('finish-type')
&& $listing->getArea()
&& $listing->getAttribute('floor')
&& $listing->getAttribute('rooms')
&& $listing->getAttribute('baths')
) {
$listingDescriptions[] = $this->translator->trans(
'layout.description.translations.property_type_section_size_finish_floor_view_room_bath',
[
'%property_type%' => $listing->getPropertyType()->getTitle(),
'%section%' => $listing->getSection()->getTitle(),
'%size%' => $listing->getArea().' '.$this->translator->trans($sizeUnit, [], null, $locale),
'%finish%' => $this->translator->trans($listing->getAttribute('finish-type')->getValue(), [], null, $locale),
'%view%' => $view,
'%floor%' => $this->numberToWordService->convertToRank($listing->getAttribute('floor')->getValue(), $locale),
'%rooms%' => $this->numberToWordService->convertToNum($listing->getAttribute('rooms')->getValue(), $locale),
'%baths%' => $this->numberToWordService->convertToNum($listing->getAttribute('baths')->getValue(), $locale),
],
null,
$locale
);
}
if (
$listing->getLocation()
&& $listing->getSection()
&& $listing->getPropertyType()
&& $listing->getAttribute('finish-type')
&& $listing->getArea()
&& $listing->getAttribute('floor')
&& $listing->getAttribute('rooms')
&& $listing->getAttribute('baths')
) {
$listingDescriptions[] = $this->translator->trans('layout.description.translations.finish_size_location_view_floor_room_bath', [
'%in%' => $listing->getLocation() ? $this->translator->trans('listing.in', [], null, $locale) : null,
'%location%' => $listing->getLocation() ? $listing->getLocation()->getTitle() : null,
'%property_type%' => $listing->getPropertyType()->getTitle(),
'%section%' => $listing->getSection()->getTitle(),
'%size%' => $listing->getArea().' '.$this->translator->trans($sizeUnit, [], null, $locale),
'%finish%' => $this->translator->trans($listing->getAttribute('finish-type')->getValue(), [], null, $locale),
'%view%' => $view,
'%floor%' => $this->numberToWordService->convertToRank($listing->getAttribute('floor')->getValue(), $locale),
'%rooms%' => $this->numberToWordService->convertToNum($listing->getAttribute('rooms')->getValue(), $locale),
'%baths%' => $this->numberToWordService->convertToNum($listing->getAttribute('baths')->getValue(), $locale),
], null, $locale);
}
if (
$listing->getArea()
) {
$listingDescriptions[] = $this->translator->trans('layout.description.translations.property_type_section_location_size_view', [
'%in%' => $listing->getLocation() ? $this->translator->trans('listing.in', [], null, $locale) : null,
'%location%' => $listing->getLocation() ? $listing->getLocation()->getTitle() : null,
'%property_type%' => $listing->getPropertyType()->getTitle(),
'%section%' => $listing->getSection()->getTitle(),
'%size%' => $listing->getArea().' '.$this->translator->trans($sizeUnit, [], null, $locale),
'%view%' => $view,
], null, $locale);
}
if (
$listing->getLocation()
&& $listing->getPropertyType()
&& $listing->getAttribute('finish-type')
&& $listing->getArea()
&& $listing->getAttribute('floor')
&& $listing->getAttribute('rooms')
&& $listing->getAttribute('baths')
) {
$listingDescriptions[] = $this->translator->trans('layout.description.translations.propery_type_finish_size_location_view_floor_room_bath', [
'%in%' => $listing->getLocation() ? $this->translator->trans('listing.in', [], null, $locale) : null,
'%location%' => $listing->getLocation() ? $listing->getLocation()->getTitle() : null,
'%property_type%' => $listing->getPropertyType()->getTitle(),
'%size%' => $listing->getArea().' '.$this->translator->trans($sizeUnit, [], null, $locale),
'%finish%' => $this->translator->trans($listing->getAttribute('finish-type')->getValue(), [], null, $locale),
'%view%' => $view,
'%floor%' => $this->numberToWordService->convertToRank($listing->getAttribute('floor')->getValue(), $locale),
'%rooms%' => $this->numberToWordService->convertToNum($listing->getAttribute('rooms')->getValue(), $locale),
'%baths%' => $this->numberToWordService->convertToNum($listing->getAttribute('baths')->getValue(), $locale),
], null, $locale);
}
return !empty($listingDescriptions) ? $listingDescriptions[array_rand($listingDescriptions)] : '';
}
/**
* Generate auto-translation to listing address.
*
* @param null $locale
*/
public function listingAddressTranslations($listing, $locale = null)
{
if (null == $locale) {
$locale = $this->requestStack->getCurrentRequest()->getLocale();
}
$this->em->refresh($listing->getLocation());
if ($listing->getLocation()->getParent()) {
$this->em->refresh($listing->getLocation()->getParent());
$listingAddresses[] = $listing->getLocation()->getTitle().$this->translator->trans('common.comma_separator', [], null, $locale).$listing->getLocation()->getParent()->getTitle();
} else {
$listingAddresses[] = $listing->getLocation()->getTitle();
}
return $listingAddresses[array_rand($listingAddresses)];
}
/**
* Get all listing translations.
*
* @return array
*/
public function getListingTranslations(Listing $listing)
{
$currentLocale = $this->requestStack->getCurrentRequest()->getLocale();
$listingTranslations = [];
// Store current listing translation
$listingTranslations[$currentLocale] = $listing;
// Identifying Submitted Locale
foreach ($this->locales as $locale) {
if ($locale != $currentLocale) {
$this->translatableListener->setTranslatableLocale($locale);
$this->em->refresh($listing);
/* @var $listingTranslation \Aqarmap\Bundle\ListingBundle\Entity\Listing */
$listingTranslations[$locale] = $this->cloneListing($listing);
}
}
$this->translatableListener->setTranslatableLocale($currentLocale);
$this->em->refresh($listing);
return $listingTranslations;
}
/**
* Validate All listing Photo orders and if empty initialize it.
*/
public function validatePhotoOrders(Listing $listing): void
{
/** @var ListingPhoto $listingPhoto */
foreach ($listing->getPhotos() as $listingPhoto) {
if (!$listingPhoto->getOrder()) {
$maxOrder = $this->getMaxListingPhotoOrder($listing);
$listingPhoto->setOrder($maxOrder + 1);
$this->em->persist($listingPhoto);
$this->em->flush();
}
}
}
/**
* @param bool $disableDeleted
*
* @return array
*
* @throws \Exception
*
* @internal param array $criteria
*/
public function getUserListingsRatesCounts(User $user, $startDate = null, ?string $period = null, $disableDeleted = true)
{
$insights = $this->getUserListingsInsights($user, $startDate, $period, $disableDeleted);
$averageClickRate = $insights['impressions'] ? round(($insights['views'] / $insights['impressions']) * 100, 2) : 0;
$averageContactRate = $insights['views'] ? round(($insights['leads'] / $insights['views']) * 100, 2) : 0;
return [
'total_leads' => number_format((float) $insights['leads']),
'total_views' => number_format((float) $insights['views']),
'total_impressions' => number_format((float) $insights['impressions']),
'average_click_rate' => $averageClickRate,
'average_contact_rate' => $averageContactRate,
'average_cost_per_lead' => (float) $insights['average_cost_per_lead'],
];
}
/**
* @param bool $disableDeleted
*
* @return array
*
* @throws \Exception
*
* @internal param array $criteria
*/
public function getUserListingsInsights(User $user, $startDate = null, ?string $period = null, $disableDeleted = true)
{
// Count All Leads
$leadRepository = $this->em->getRepository(Lead::class);
$totalLeads = $leadRepository->getLeadsCountBaseQueryBuilder([$user->getId()])->getQuery()->getSingleScalarResult() ?? 0;
if ($disableDeleted) {
if ($this->em->getFilters()->isEnabled('softdeleteable')) {
$this->em->getFilters()->disable('softdeleteable');
}
}
// Count all Impressions
$totalImpressions = $this->getTotalImpressionsCount([
'user' => $user, 'startDate' => $startDate, 'period' => $period,
]);
// Count All Views
$totalViews = $this->listingRepository->countViews([
'user_id' => $user,
'start_date' => $startDate,
'end_date' => date('Y-m-d'),
'excludeParent' => false,
'status' => 'ONLYLIVE' == $period ? ListingStatus::LIVE : null,
]);
// Get Average Cost per Lead
$averageCostPerLead = $this->costPerLeadService->getAverageCostPerLead($user, [
'user' => $user,
'haveLeads' => true,
'status' => 'ONLYLIVE' == $period ? ListingStatus::LIVE : null,
'inLastDays' => !empty($period) ? self::MAP_STRING_DAYS_WITH_NUMBER[$period] : null,
]);
return [
'impressions' => $totalImpressions,
'views' => $totalViews,
'leads' => $totalLeads,
'average_cost_per_lead' => $averageCostPerLead,
];
}
public function addLiteListingTitleAndDescriptionTranslation(Listing $listing): void
{
// arabic
$listing->setTitle($this->listingTitleTranslations($listing, 'ar'));
$listing->setDescription($this->listingDescriptionTranslations($listing, 'ar'));
$this->saveListing($listing, true);
// english
$listing->setTranslatableLocale('en');
$this->em->refresh($listing);
$listing->setTitle($this->listingTitleTranslations($listing, 'en'));
$listing->setDescription($this->listingDescriptionTranslations($listing, 'en'));
$this->saveListing($listing, true);
}
/**
* Generate custom pagination.
*
* @param array $data
* @param int $limit
* @param null $page
* @param array $options
* @param array $parameters
*
* @return \Knp\Component\Pager\Pagination\PaginationInterface
*/
public function generateCustomPagination($data = [], $limit = 10, $page = null, $options = [], $parameters = [])
{
$pagination = $this->paginator->paginate(
$data,
$page,
$limit
);
$pagination->setTotalItemCount(\count($data));
foreach ($options as $option => $value) {
$pagination->setPaginatorOptions([
$option => $value,
]);
}
foreach ($parameters as $parameter => $value) {
$pagination->setParam($parameter, $value);
// Adding tab anchor
if ($parameters['tab']) {
$pagination->setParam('_fragment', $value);
}
}
return $pagination;
}
public function generateCustomPaginationForQuery($query, int $limit = 10, $page = 1, $options = [], $parameters = [])
{
$pagination = $this->paginator->paginate(
$query,
$page,
$limit
);
foreach ($options as $option => $value) {
$pagination->setPaginatorOptions([
$option => $value,
]);
}
foreach ($parameters as $parameter => $value) {
$pagination->setParam($parameter, $value);
// Adding tab anchor
if ($parameters['tab']) {
$pagination->setParam('_fragment', $value);
}
}
return $pagination;
}
/**
* Handle delete listing action.
*
* @return ListingAction
*/
public function deleteListingReason(Listing $listing, $formData)
{
switch ($formData['delete_reason']) {
case 'sold':
$this->changeStatus($listing, ListingStatus::SOLD, false);
$listing->setSourceOfSale($formData['source_of_sale']);
$listing->setFinalPrice($formData['final_selling_price']);
break;
case 'other':
$listing->setDeleteReasonDetails($formData['other_reason']);
break;
}
$listing->setDeleteReason($formData['delete_reason']);
$this->em->persist($listing);
$this->em->flush();
return $listing;
}
/**
* Subtract credit and change status.
*/
public function subtractCreditAndChangeStatus(Listing $listing): void
{
// Don't double deduct credits
if ($listing->getPublicationCredit()) {
return;
}
$listingRule = $this->matcher->match($listing);
$available_balance = $this->creditManager->getBalance($listing->getUser());
// Making sure that the approved payment will cover the listing fees
if ($listingRule['publication_fees'] <= $available_balance) {
$credits = $this->creditManager->deduction($listing->getUser(), $listingRule['publication_fees'], 'Listing Fees');
foreach ($credits as $credit) {
if ($credit instanceof Credit) {
$credit->setStatus(CreditStatus::SUCCESS);
$this->em->persist($credit);
$this->em->flush($credit);
$this->addFeature($listing, ListingFeatures::PAID, null, $credit);
}
}
$listing->setCategory(ListingCategories::PAID);
$this->saveListing($listing);
}
}
/**
* My Listings Tabs.
*
* @param float $version
*
* @return array
*/
public function myListingsTabs($version, $user = null)
{
$listingRepository = $this->em->getRepository(Listing::class);
$expired = [
'status' => ListingStatus::EXPIRED,
'label' => $this->translator->trans(ListingStatus::EXPIRED_LABEL),
];
$pending = [
'status' => ListingStatus::PENDING,
'label' => $this->translator->trans(ListingStatus::PENDING_LABEL),
];
$pendingPhoto = [
'status' => ListingStatus::PENDING_PHOTOS,
'label' => $this->translator->trans(ListingStatus::PENDING_PHOTOS_LABEL),
];
if ($version >= 2.13) {
// Live (Default tab)
$tabs[] = [
'id' => 1,
'name' => $this->translator->trans('label.status.live'),
'statuses' => [
[
'status' => ListingStatus::LIVE,
'label' => $this->translator->trans(ListingStatus::LIVE_LABEL),
],
],
];
// Pending
$tabs[] = [
'id' => 2,
'name' => $this->translator->trans('label.status.pending'),
'statuses' => [
$pending,
$pendingPhoto,
[
'status' => ListingStatus::PENDING_PAYMENT,
'label' => $this->translator->trans(ListingStatus::PENDING_PAYMENT_LABEL),
],
],
];
// Rejected Section
$tabs[] = [
'id' => 3,
'name' => $this->translator->trans('label.status.reject'),
'statuses' => [
[
'status' => ListingStatus::REJECTED,
'label' => $this->translator->trans(ListingStatus::REJECTED_LABEL),
],
],
'listings_count' => $user instanceof User ?
(int) $listingRepository->ListingsCountQuery(['user_id' => $user->getId(), 'status' => ListingStatus::REJECTED]) : 0,
];
// Expired Section
$tabs[] = [
'id' => 4,
'name' => $this->translator->trans('label.status.expired'),
'statuses' => [
$expired,
[
'status' => ListingStatus::SOLD,
'label' => $this->translator->trans(ListingStatus::SOLD_LABEL),
],
],
];
// Deleted Section
$tabs[] = [
'id' => 5,
'name' => $this->translator->trans('label.status.user_deleted'),
'statuses' => [
[
'status' => ListingStatus::USER_DELETED,
'label' => $this->translator->trans(ListingStatus::USER_DELETED_LABEL),
],
],
];
} else {
// Live and pending tab (Default tab)
$tabs[] = [
'id' => 1,
'name' => $this->translator->trans('layout.my_active_listings'),
'statuses' => [
[
'status' => ListingStatus::LIVE,
'label' => $this->translator->trans(ListingStatus::LIVE_LABEL),
],
$pending,
$pendingPhoto,
[
'status' => ListingStatus::PENDING_PAYMENT,
'label' => $this->translator->trans(ListingStatus::PENDING_PAYMENT_LABEL),
],
],
];
// Expired Section
$tabs[] = [
'id' => 2,
'name' => $this->translator->trans('layout.my_expired_listings'),
'statuses' => [
$expired,
[
'status' => ListingStatus::SOLD,
'label' => $this->translator->trans(ListingStatus::SOLD_LABEL),
],
],
];
// Deleted Section
$tabs[] = [
'id' => 3,
'name' => $this->translator->trans('layout.my_deleted_listings'),
'statuses' => [
[
'status' => ListingStatus::USER_DELETED,
'label' => $this->translator->trans(ListingStatus::USER_DELETED_LABEL),
],
],
];
}
return $tabs;
}
/**
* Dynamic set listing relations.
*
* @param array $fields
*
* @return $this
*/
public function updateRelations($fields)
{
if (!isset($fields['_relation'])) {
return $this;
}
foreach ($fields['_relation'] as $field => $val) {
$field = ucwords($field);
$entity = $this->em->getRepository("AqarmapListingBundle:{$field}")->find($val);
$fn = 'set'.$field;
if (method_exists($this->listing, $fn) && $entity) {
$this->listing->$fn($entity);
}
}
return $this;
}
/**
* Update listing.
*
* @return Listing
*/
public function update(Listing $listing, array $fields)
{
$this->listing = $listing;
$this->updateFields($fields)
->updateAttributes($fields)
->updateRelations($fields)
->updatePhone($fields)
->saveListing($listing);
return $listing;
}
/**
* Generate auto-translation to listing title in all locales.
*/
public function getAllListingTitleTranslations(Listing $listing)
{
$listingTitles = [];
foreach ($this->locales as $locale) {
$this->translatableListener->setTranslatableLocale($locale);
$this->em->refresh($listing->getLocation());
$this->em->refresh($listing->getPropertyType());
$this->em->refresh($listing->getSection());
$listingTitles[$locale] = $this->generateTranslatedListingTitle($listing, $locale);
}
return $listingTitles;
}
/*
* Make a listing featured for free.
*
* @param Listing $listing
*
* @return bool
*/
public function AddFeaturedListingForFree(Listing $listing, $featuredType)
{
$listingRule = $this->listingFeatureService->getFeaturedListingRules($listing, [$featuredType ? $featuredType : 'featured']);
$listingOwner = $listing->getUser();
$credits = $this->getCreditManager()->deduction(
$listingOwner,
(float) 0,
'Adding a featured listing for free',
true
);
if (!$listingRule[$featuredType]['duration']) {
$listingRule[$featuredType]['duration'] = 10;
}
$expiredAt = null;
if (ListingStatus::LIVE == $listing->getStatus()) {
if (!$listing->getParent()) {
$expiredAt = new \DateTime('+'.$listingRule[$featuredType]['duration'].' days');
} else {
$expiredAt = new \DateTime('+10 years');
}
}
foreach ($credits as $credit) {
$this->addFeature($listing, $listingRule[$featuredType]['listingFeature'], $expiredAt, $credit);
}
$this->saveListing($listing, true);
return true;
}
/**
* @return bool
*/
public function isPumpUpAvailable(array $listingRules)
{
$pumpUpFees = $listingRules['pump_up_fees'];
$pumpUpDuration = $listingRules['pump_up_duration'];
$pumpUpOccurrence = $listingRules['pump_up_occurrence'];
if (
empty($pumpUpFees)
|| empty($pumpUpDuration)
|| empty($pumpUpOccurrence)
) {
return false;
}
return true;
}
/**
* @return bool
*/
public function isAffordable(User $listingOwner, string $fees)
{
$creditManager = $this->creditManager;
$availableBalance = $creditManager->getBalance($listingOwner);
$settingsManager = $this->settings;
$enforceCredit = $settingsManager->getSetting('features', 'enforce_credits_expiration');
if (
$fees > $availableBalance
|| ($enforceCredit && 0 === $listingOwner->getAbsoluteCreditExpiryDays())
) {
return false;
}
return true;
}
/**
* @return array
*/
public function getListingRules(Listing $listing)
{
return $this->matcher->match($listing);
}
/**
* @return bool
*/
public function isListingEvaluable(Listing $listing)
{
return $listing->getPropertyType()->getIsEvaluable()
&& $listing->getPrice()
&& !\in_array($listing->getStatus(), [ListingStatus::DRAFT, ListingStatus::REJECTED, ListingStatus::ADMIN_DELETED, ListingStatus::USER_DELETED, ListingStatus::EXPIRED]);
}
/**
* @return LocationStatistics[]|array|null
*/
public function evaluateListingPrice(Listing $listing)
{
$locationStatisticsRepository = $this->em->getRepository('AqarmapNeighborhoodBundle:LocationStatistics');
$locationStatistics = $locationStatisticsRepository->findBy(['location' => $listing->getLocation()]);
if (!$locationStatistics) {
/**
* @var LocationStatistics $locationStatistics
*/
$locationStatistics = $this->getListingNearestStatistics($listing);
}
return $locationStatistics ? $locationStatistics->getAvgPrice() : null;
}
/**
* @return bool
*/
public function requiresFeaturingReview(int $featuredType)
{
$requiresFeaturingApproval = $this->settings->getSetting('requires_featuring_approval', $featuredType);
if ($requiresFeaturingApproval) {
return true;
}
return false;
}
/**
* @return bool
*/
public function userHasFeesToFeature(User $user, Listing $listing)
{
$listingRules = $this->listingFeatureService->getFeaturedListingRules($listing, ['featured', 'premium', 'sponsored', 'spotlight', 'sold_by_owner']);
$haveFeesToFeature = false;
foreach ($listingRules as $listingRule) {
if ($fees = $listingRule['fees']) {
$haveFeesToFeature = $this->isAffordable($user, $fees);
if ($haveFeesToFeature) {
break;
}
}
}
return $haveFeesToFeature;
}
/**
* @return array
*/
public function getLeadAnalytics(Listing $listing)
{
$supportedFeatured = array_keys(ListingFeaturedTypes::getAnalyticsChoices());
$dataLayer['listingSection'] = $listing->getSection() ? $listing->getSection()->getSlug() : null;
if ($listing->getIsTopPicks()) {
$dataLayer['listingSegment'] = self::TOP_PICKS_LABEL;
return $dataLayer;
}
if (\in_array($listing->getFeatured(), $supportedFeatured)) {
$dataLayer['listingSegment'] = ListingFeaturedTypes::getAnalyticsLabel($listing->getFeatured());
return $dataLayer;
}
array_keys(ListingCategories::getAnalyticsChoices());
if (\in_array($listing->getCategory(), $supportedFeatured)) {
$dataLayer['listingSegment'] = ListingCategories::getAnalyticsLabel($listing->getCategory());
return $dataLayer;
}
if ($listing->getPendingPaymentStatus()) {
$dataLayer['listingSegment'] = 'passfree';
return $dataLayer;
}
return $dataLayer;
}
public function pauseFeatured(Listing $listing): void
{
$listingFeatures = $listing->getListingFeatures();
$today = new \DateTime();
/** @var ListingFeature $listingFeature */
foreach ($listingFeatures as $listingFeature) {
if (
!$listingFeature->getPausedAt()
&& $listingFeature->getExpiresAt() >= $today
&& $listingFeature->getCreatedAt() <= $today
) {
$listingFeature->setPausedAt($today);
$this->em->persist($listingFeature);
}
}
}
public function unPauseFeatured(Listing $listing): void
{
$listingFeatures = $listing->getListingFeatures();
/** @var ListingFeature $listingFeature */
foreach ($listingFeatures as $listingFeature) {
$pausedAt = $listingFeature->getPausedAt();
$expiredAt = $listingFeature->getExpiresAt();
if (!$pausedAt) {
continue;
}
$remainingDays = $pausedAt->diff($expiredAt);
$newExpiredAt = (new \DateTime());
$newExpiredAt->add($remainingDays);
$listingFeature->setExpiresAt($newExpiredAt);
$listingFeature->setPausedAt(null);
$this->em->persist($listingFeature);
}
}
public function handleListingUnFeaturing(Listing $listing): void
{
$listing->setFeatured(null);
$now = new \DateTime();
/** @var ListingFeature $listingFeature */
foreach ($listing->getListingFeatures() as $listingFeature) {
if ($listingFeature->getExpiresAt() && $listingFeature->getExpiresAt() > $now) {
$listingFeature->setExpiresAt($now);
$this->em->persist($listingFeature);
}
}
$this->em->flush();
}
/**
* Check If Listing Featuring Rejection Reason is exist.
*
**/
public function isRejectionReasonExistPerListing(Listing $listing, Rejection $rejection)
{
$currentRejections = $listing->getRejections();
foreach ($currentRejections as $currentRejection) {
if ($rejection == $currentRejection) {
return true;
}
}
return false;
}
/**
* Set Listing Featuring Rejection Reason.
*
**/
public function setListingFeaturingRejectionReason(Listing $listing, Rejection $rejection): void
{
$existRejection = $this->isRejectionReasonExistPerListing($listing, $rejection);
if (!$existRejection) {
$listing->addRejection($rejection);
$this->em->persist($listing);
$this->em->flush();
}
}
public function setUserActivities(?array $listings, ?array $listingsIds): ?array
{
$this->userManager = $this->getUserManagerService();
if (!$listingsIds || !$listings || !$this->userManager->isLoggedIn()) {
return $listings;
}
$criteria = [
'user' => $this->tokenStorage->getToken()->getUser(),
'listingsIds' => $listingsIds,
];
$listings = $this->setlistingNote($listings, $criteria);
return $this->setFavouriteListings($listings, $criteria);
}
public function setUserActivitiesFromPagination(SlidingPagination $pagination): SlidingPagination
{
$listings = [];
$listingsIds = [];
$items = $pagination->getItems();
$userKey = null;
$favouriteOwner = null;
$item = null;
foreach ($items as $item) {
if ($item instanceof NonListingLeadInterface) {
continue;
}
$listingsIds[] = $item->getListing()->getId();
$listings[] = $item->getListing();
}
if ($item) {
$favouriteOwner = $item->getUser()->getFullName();
if (method_exists($item, 'getUserKey')) {
$userKey = $item->getUserKey();
}
}
$listingsWithActivities = $this->setUserActivities($listings, $listingsIds);
$pagination->setItems($listingsWithActivities);
$pagination->setCustomParameters([
'user_key' => $userKey,
'favourite_owner' => $favouriteOwner,
]);
return $pagination;
}
public function updateRateStatus(Listing $listing, ?int $status)
{
$listing->setRateStatus($status);
return $this->persist($listing);
}
public function updateIsRateReviewed(Listing $listing, bool $isRateReviewed)
{
$listing->setIsRateReviewed($isRateReviewed);
return $this->persist($listing);
}
public function persist(Listing $listing)
{
$this->em->persist($listing);
return $this->em->flush();
}
/**
* @return Response
*/
public function export(IterableResult $listings)
{
$handle = fopen('php://memory', 'r+');
fputcsv($handle, [
'Listing ID',
'Title',
'Status',
'Last Action Date',
'User ID',
'User Email',
]);
foreach ($listings as $listing) {
$listing = current($listing)['listing'];
fputcsv($handle, [
$listing->getId(),
$listing->getTitle(),
$this->translator->trans($listing->getStatusLabel()),
$listing->getUpdatedAt() ? $listing->getUpdatedAt()->format('Y-m-d H:i:s') : null,
$listing->getUser()->getId(),
$listing->getUser()->getEmail(),
]);
$this->em->detach($listing);
}
rewind($handle);
$content = stream_get_contents($handle);
fclose($handle);
return new Response($content, Response::HTTP_OK, [
'Content-Type' => 'application/force-download',
'Content-Disposition' => 'attachment; filename="Paid Listings - '.date('Y-m-d H-i-s').'.csv"',
]);
}
public function findIterable(array $criteria)
{
return $this
->em
->getRepository(Listing::class)
->search($criteria)
->getQuery()
->iterate();
}
/**
* @return Listing
*/
public function updateRejectedAt(Listing $listing, \DateTime $rejectedAt)
{
$listing->setRejectedAt($rejectedAt);
$this->saveListing($listing);
return $listing;
}
/**
* Get Range Of Percentage Value.
*/
public function getRangeOfPercentageOfValue(?int $value = null, int $percentage = 10): array
{
$valueToGetRange = ($percentage / 100) * $value;
return [
'min' => round($value - $valueToGetRange),
'max' => round($value + $valueToGetRange),
];
}
/**
* Get Similar Listings Data.
*
* @return array
*/
public function getSimilarListingsData(Listing $listing, int $limit = 10)
{
$similarListingsCriteria = $this->prepareSimilarListingsCriteria($listing);
return $this->listingRepository->getSimilarListingsData($similarListingsCriteria, $limit)->getResult();
}
/**
* Get Similar Listings Data.
*/
public function countSimilarListings(Listing $listing, int $limit = 10): int
{
$similarListingsCriteria = $this->prepareSimilarListingsCriteria($listing);
return (int) $this->listingRepository->getSimilarListingsData($similarListingsCriteria, $limit, true)->getSingleScalarResult();
}
/**
* @throws ORMException
* @throws OptimisticLockException
*/
public function setProjectUnitsOwner(Listing $project): void
{
if (!$project->isProject() || !$project->getChildren()) {
return;
}
$owner = $project->getUser();
/** @var Listing $unit */
foreach ($project->getChildren() as $unit) {
$unit->setUser($owner);
$this->em->persist($unit);
$this->em->flush();
}
}
public function incrementViewsCounters(Listing $listing): void
{
$listing->setViews($listing->getViews() + 1);
$this->em->persist($listing);
$this->em->flush();
}
/**
* @return SlidingPagination
*/
public function setStaticNotesFromPaginator(SlidingPagination $pagination, array $listingsWithNotes)
{
$items = $pagination->getItems();
$listings = [];
/* @var Listing|null $item */
foreach ($items as $listing) {
if ($listing instanceof Listing) {
$listing->setUserNote($this->getStaticNoteByListingId($listing->getId(), $listingsWithNotes));
$listings[] = $listing;
}
}
$pagination->setItems($listings);
return $pagination;
}
public function getOtherUnits(Listing $listing, ?string $locale = 'en'): array
{
$otherUnits = $this->em->getRepository(Listing::class)->getOtherUnits($listing, $locale) ?? [];
return array_group_by($otherUnits, 'propertyTypeTitle');
}
/**
* Publish Sync Project Listing to Queue.
*/
public function publishSyncProjectListingToQueue(array $criteria): void
{
$this->messageBus->dispatch(new SyncListingProjectMessage($criteria));
}
/**
* Deduct Credit.
*
* @return Response
*/
public function deductCredit(array $criteria)
{
$listingRule = $this->matcher->match($criteria['listing']);
$userPackages = $this->em->getRepository(\Aqarmap\Bundle\UserBundle\Entity\UserPackages::class);
$available_credit = $userPackages->getTotalCreditsByUser($criteria['user']);
$enforceCredit = $this->settings->getSetting('features', 'enforce_credits_expiration');
$this->translator->setLocale($criteria['locale']);
if ($enforceCredit && 0 === $criteria['user']->getAbsoluteCreditExpiryDays()) {
return [
'status' => 'credit_can_not_use',
'message' => $this->translator->trans('credit.credit_can_not_use_without_link'),
];
}
if ($criteria['listing']->getPublicationCredit()) {
return [
'status' => 'already_paid',
'message' => $this->translator->trans('credit.already_paid'),
];
}
if ($listingRule['publication_fees'] > $available_credit) {
return [
'status' => 'not_enough_credits',
'message' => $this->translator->trans('credit.not_enough_credits'),
];
}
$credits = $this->creditManager
->deduction(
$criteria['listing']->getUser(),
$listingRule['publication_fees'],
'Listing Fees'
);
if ($credits) {
foreach ($credits as $credit) {
if ($credit instanceof Credit) {
$credit->setStatus(CreditStatus::SUCCESS);
$this->addFeature($criteria['listing'], ListingFeatures::PAID, null, $credit);
}
}
return [
'status' => 'credit_deducted_successfully',
'message' => $this->translator->trans('credit.credit_deducted_successfully'),
];
}
return [
'status' => 'something_wrong_happened',
'message' => 'Something Wrong happened While deduction',
];
}
/**
* @return float|int
*
* @throws \Doctrine\ODM\MongoDB\MongoDBException
* @throws \Exception
*/
public function getApprovalRejectionWaitingTime(array $criteria)
{
$activityRepo = $this->dm->getRepository(ListingActivityLog::class);
$approvalsCount = $activityRepo->filter($criteria, true)->execute();
$arwtResult = $activityRepo->getSumActionTime($criteria)->execute()->getSingleResult();
$arwt = isset($arwtResult, $arwtResult['actionTime']) ? $arwtResult['actionTime'] : 0;
return $approvalsCount ? $arwt / $approvalsCount : 0;
}
public function delete(Listing $listing, array $reasons): void
{
$this->changeStatus($listing, ListingStatus::USER_DELETED, false);
$listing->setDeleteReasonDetails($reasons['other_reason']);
$listing->setDeleteReason($reasons['reason']);
$listing->setFinalPrice($reasons['final_price']);
$listing->setSourceOfSale($reasons['source_of_sale']);
$this->em->persist($listing);
$this->em->flush();
}
public function handleListingSortingOptions(): array
{
$sortingOptions = ListingSortingOptions::buildSortingOptions();
/**
* @var ListingSortingValues $sortingOption
*/
foreach ($sortingOptions as $sortingOption) {
$options = [];
foreach ($sortingOption->getOptions() as $option => $value) {
$options[] = new ListingSortingOption($this->translator->trans($option), $value);
}
$sortingOption->setOptions($options);
}
return $sortingOptions;
}
/**
* Prepare Listing Statuses Array.
*
* @param array $userListingStatuses
*/
public function prepareListingStatusesArray($userListingStatuses): array
{
return [
['status' => ListingStatus::getName(ListingStatus::LIVE), 'count' => 0],
['status' => ListingStatus::getName(ListingStatus::PENDING), 'count' => 0],
['status' => ListingStatus::getName(ListingStatus::DRAFT), 'count' => 0],
['status' => 'deleted', 'count' => 0],
['status' => ListingStatus::getName(ListingStatus::EXPIRED), 'count' => 0],
['status' => ListingStatus::getName(ListingStatus::REJECTED), 'count' => $userListingStatuses['count']],
];
}
/**
* @return bool
*/
public function hasValidFreeListing(User $user, Listing $listing)
{
if ($this->em->getRepository(Listing::class)
->hasValidListing($user, $listing)
) {
return true;
}
return false;
}
/**
* @return bool
*/
public function isAvailableToAddFreeListing(string $phone, int $userId, ?Listing $listing = null)
{
$freeListingRepository = $this->em->getRepository(FreeListing::class);
$isPhoneExist = $freeListingRepository->findBy(['phoneNumber' => $phone], ['id' => 'DESC'], 1, 0);
if ((!empty($isPhoneExist) && $this->getDatesDifferenceInMonth($isPhoneExist[0]->getCreatedAt()) < self::MONTH_RANGE)
|| ($listing && $listing->getPublicationCredit())
) {
return false;
}
$user = $this->em->getRepository(User::class)->findOneBy(['id' => $userId]);
$otpService = $this->otpService;
if ($otpService->isCodeSentForListing($listing)) {
return true;
}
return $otpService->sendVerificationCode($user, $phone, $listing);
}
public function createFirstListingForFree(Listing $listing): void
{
$listing->setCategory(ListingCategories::FIRST_LISTING_FOR_FREE);
$listingEvent = new ListingEvent($listing);
$this->dispatcher->dispatch($listingEvent, 'aqarmap.listing.submitted');
$this->saveListing($listing);
$credits = $this->creditManager->deduction(
$listing->getUser(),
0,
CreditDescriptions::FIRST_LISTING_FOR_FREE_LABEL,
CreditStatus::SUCCESS
);
foreach ($credits as $credit) {
if ($credit instanceof Credit) {
$this->addFeature($listing, ListingFeatures::PAID, null, $credit);
}
}
$this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.first_listing_for_free');
}
public function isListingUserEbawab(Listing $listing): bool
{
return $this->isListingEmailIncludes($listing, 'ebawab@aqarmap.com');
}
public function isListingUserManualScraped(Listing $listing): bool
{
return $this->isListingEmailIncludes($listing, '+cc@aqarmap.com');
}
/**
* Update listing seller role.
*/
public function updateSellerRole(User $user, int $sellerRole): void
{
if ($this->em->getFilters()->isEnabled('softdeleteable')) {
$this->em->getFilters()->disable('softdeleteable');
}
$this->listingRepository->updateSellerRole($user, $sellerRole);
$this->em->getFilters()->enable('softdeleteable');
}
/**
* Get listings by ids sorted as is.
*
* @return QueryBuilder
*/
public function getListingsByIdsSortedAsIs(array $parentIds)
{
$commaSeparatedParentIds = implode(',', $parentIds);
return $this->listingRepository->getListingsByIdsSortedAsIs($parentIds, $commaSeparatedParentIds);
}
public function setEligibleMortgageToNull(Listing $listing): void
{
// force delete EligibleForMortgage to null
$listing->setEligibleForMortgage();
$this->saveListing($listing);
}
public function approveListing(Listing $listing): void
{
if (!\in_array($listing->getStatus(), self::ALLOWED_STATUS_FOR_APPROVAL)) {
return;
}
$this->changeStatus($listing, ListingStatus::LIVE);
if ($listing->getParent()) {
$this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.project.re_calculate.min_price_area');
$this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.project.calculate.average.pricePerMeter');
}
}
public function extractListingCriteria(Listing $listing): array
{
$criteria = [];
$criteria['status'] = $listing->getStatus();
$criteria['section'] = $listing->getSection()->getId();
$criteria['propertyType'] = $listing->getPropertyType()->getId();
$criteria['location'] = $listing->getLocation()->getId();
$criteria['price']['min'] = $listing->getPrice() - $listing->getPrice() / 30;
$criteria['price']['max'] = $listing->getPrice() + $listing->getPrice() / 30;
$criteria['orderBy'] = ['l.publishedAt' => 'DESC'];
$criteria['excludedListingsIds'][] = $listing->getId();
return $criteria;
}
public function getLiveListingsCountPerUser(array $listings): array
{
if (empty($listings)) {
return [];
}
$listingsUserIds = [];
foreach ($listings as $listing) {
if (!$listing instanceof Listing) {
return [];
}
$listingsUserIds[] = $listing->getUser()->getId();
}
if ($this->em->getFilters()->isEnabled('softdeleteable')) {
$this->em->getFilters()->disable('softdeleteable');
}
$listingCountPerUser = $this->listingRepository->getLiveListingsCountPerUser(array_unique($listingsUserIds));
$this->em->getFilters()->enable('softdeleteable');
return $this->turnIntoUserToListingsMap($listingCountPerUser);
}
/**
* @return float
*/
public function getListingsPhotosAverage(User $user)
{
$criteria = ['user_id' => $user->getId(), 'status' => ListingStatus::LIVE];
$listingsCount = $this->listingRepository->ListingsCountQuery($criteria);
$photosCount = $this->listingPhotoRepository->getListingsPhotosCount($criteria);
if (empty($photosCount) || empty($listingsCount)) {
return 0;
}
$average = (float) $photosCount / $listingsCount;
return (float) number_format($average, self::DECIMALS_PLACES_NUMBER);
}
/**
* @throws OptimisticLockException
* @throws ORMException
*/
public function initializeListingForCloning(Listing $targetListing, PropertyType $propertyType, Section $section, UserInterface $user): Listing
{
$listing = new Listing();
$listing->setLocation($targetListing->getLocation());
$listing->setPropertyType($propertyType);
$listing->setSection($section);
$listing->setUser($user);
$listing->setStatus(ListingStatus::DRAFT);
if ($targetListing->getParent()) {
$listing->setMarketPropertyType(MarketPropertyTypes::PRIMARY);
$listing->setPrice($targetListing->getPrice());
$listing->setArea($targetListing->getArea());
$listing->setPaymentMethod($targetListing->getPaymentMethod());
$listing->setPropertyView($targetListing->getPropertyView());
} elseif (!$this->listingRepository->isCompound($targetListing)) {
$listing = $this->cloneAttributes($targetListing, $listing);
$listing->setPaymentMethod($targetListing->getPaymentMethod());
$listing->setMarketPropertyType($targetListing->getMarketPropertyType());
}
$this->saveListing($listing);
return $listing;
}
public function cloneAttributes(Listing $sourceListing, Listing $targetListing): Listing
{
foreach ($sourceListing->getAttributes() as $attribute) {
$targetAttribute = new ListingAttribute();
$targetAttribute->setCustomField($attribute->getCustomField());
$targetAttribute->setValue($attribute->getValue());
$targetListing->addAttribute($targetAttribute);
}
return $targetListing;
}
public function buildUnitCustomAttributes(Listing $entity)
{
$custom_fields = $this->em->getRepository(\Aqarmap\Bundle\ListingBundle\Entity\CustomField::class)->findAll();
$exists_attributes = [];
foreach ($entity->getAttributes() as $listing_attribute) {
if (\in_array($listing_attribute->getCustomField()->getName(), ListingCustomFields::getFormattedAttributesNames())) {
$exists_attributes[] = $listing_attribute->getCustomField()->getId();
}
}
foreach ($custom_fields as $custom_field) {
if (\in_array($custom_field->getId(), $exists_attributes)) {
continue;
}
if (\in_array($custom_field->getName(), ListingCustomFields::getFormattedAttributesNames())) {
$attribute = new ListingAttribute();
$attribute
->setCustomField($custom_field)
->setListing($entity);
$this->em->persist($attribute);
$entity->addAttribute($attribute);
}
}
return $entity;
}
public function addLiteListingTitleTranslation(Listing $listing, Listing $sourceListing): void
{
$listing->setTitle($this->unitTitleTranslations($listing, $sourceListing, Locales::AR));
$this->saveListing($listing, true);
$listing->setTranslatableLocale(Locales::EN);
$this->em->refresh($listing);
$listing->setTitle($this->unitTitleTranslations($listing, $sourceListing, Locales::EN));
$this->saveListing($listing, true);
}
/**
* @return Listing
*/
public function syncProject(Listing $listing, Listing $sourceListing)
{
$listing->setStatus(ListingStatus::PENDING);
$listing->setUser($sourceListing->getUser());
$listing->setVideoUrl($sourceListing->getVideoUrl());
$listing->setLocation($sourceListing->getLocation());
$listing->setExpiresAt($sourceListing->getExpiresAt());
$listing->setCenterLat($sourceListing->getCenterLat());
$listing->setCenterLng($sourceListing->getCenterLng());
$listing->setSellerRole($sourceListing->getSellerRole());
$listing->setIsCommercial($sourceListing->isCommercial());
$listing->setPaymentMethod($sourceListing->getPaymentMethod());
return $listing;
}
public function addListingPhotos(Listing $listing, array $photos): array
{
$listingPhotos = [];
$maxListingPhotoOrder = $this->getMaxListingPhotoOrder($listing);
foreach ($photos as $index => $file) {
$photo = new Photo();
$photo->setFile($file);
$listingPhoto = $this->addListingPhoto($listing, $photo, $maxListingPhotoOrder, $index);
$listingPhotos[] = $listingPhoto;
$listing->addPhoto($listingPhoto);
}
return $listingPhotos;
}
public function getSerializedLocationChildren(Location $location)
{
$locationChildren = $location ? $location->getChildren() : [];
return $this->serializer->serialize(
$locationChildren,
'json',
SerializationContext::create()
->setGroups('MiniList')
->enableMaxDepthChecks()
);
}
public function getListingPropertyTypesChips(Request $request, ?Location $location = null, ?Section $section = null, ?PropertyType $propertyType = null)
{
foreach (['location', 'section', 'propertyType'] as $param) {
$request->query->remove($param);
}
if ($location) {
$request->query->set('location', $location->getId());
}
if ($section) {
$request->query->set('section', $section->getId());
}
if ($propertyType) {
$request->query->set('propertyType', $propertyType->getId());
}
$result = [];
$types = $this->getPropertiesChipsElasticResponse($request);
$typesArray = [];
foreach ($types as $id => $doc_count) {
$typesArray[$id] = $doc_count;
}
arsort($typesArray);
$propertyTypes = $this->propertyTypeRepository->findPropertyTypes(array_keys($typesArray));
foreach ($propertyTypes as $propertyType) {
$id = $propertyType->getId();
$result[] = [
'id' => $id,
'title' => $propertyType->getTitle(),
'slug' => $propertyType->getSlug(),
'searchResultCount' => $typesArray[$id] ?? 0,
'level' => $propertyType->getLevel(),
];
}
return $result;
}
public function getLocationParents(Location $locaion, string $locale = Locales::AR)
{
return $this->locationRepository->getParentsBreadCrumbData($locaion, $locale)->getResult();
}
public function getSerializedCustomParagraph(Section $section, PropertyType $propertyType, ?Location $location = null)
{
$customParagraph = $this->customParagraphRepository->get([
'location' => $location ? $location->getId() : null,
'propertyType' => $propertyType->getId(),
'section' => $section->getId(),
'place' => CustomParagraphPlaceTypes::SEARCH_RESULTS,
])->getQuery()->setMaxResults(1)->getOneOrNullResult() ?? [];
return $this->serializer->serialize(
$customParagraph,
'json',
SerializationContext::create()
->setGroups('Default')
->enableMaxDepthChecks()
);
}
public function getFaqs(Section $section, PropertyType $propertyType, ?Location $location = null, $locale = Locales::AR)
{
$searchFaqsCriteria = new ListingSearchFaqsCriteria();
$searchFaqsCriteria->setSection($section->getId());
$searchFaqsCriteria->setPropertyType($propertyType->getId());
$searchFaqsCriteria->setLocation($location ? $location->getId() : null);
$faqData = $this->listingFaqService->generateFaqData($searchFaqsCriteria, $locale);
return [
'listings_count' => $faqData->getListingsCount(),
'featured_listings_count' => $faqData->getFeaturedListingsCount(),
'average_price_per_meter' => $faqData->getAveragePricePerMeter(),
'price_change' => $faqData->getPriceChange(),
'sub_locations' => $faqData->getSubLocations(),
'sub_property_types' => $faqData->getSubPropertyTypes(),
'top_companies' => $faqData->getTopCompanies(),
];
}
public function getSerializedResolvedSlugs(Section $section, PropertyType $propertyType, $locationsSlugs)
{
$data = [
'section' => $section,
'propertyType' => $propertyType,
'locations' => [],
];
if (!empty($locationsSlugs) && $this->validLocationSlugArray($locationsSlugs)) {
$data['locations'] = $this->locationRepository->getLocationsBySlugs($locationsSlugs);
}
return $this->serializer->serialize(
$data,
'json',
SerializationContext::create()
->setGroups('SlugResolver')
->enableMaxDepthChecks()
);
}
private function validLocationSlugArray($array): bool
{
return \count($array) > 0 && array_reduce($array, fn ($carry, $item) => $carry && \is_string($item) && '' !== $item, true);
}
private function addListingPhoto(Listing $listing, Photo $photo, int $maxOrder, int $photoIndex): ListingPhoto
{
$listingPhoto = new ListingPhoto();
$listingPhoto->setFile($photo);
$listingPhoto->setCaption($photo->getFile()->getClientOriginalName());
$listingPhoto->setOrder($maxOrder + $photoIndex + 1);
$listingPhoto->setListing($listing);
return $listingPhoto;
}
/**
* @param Listing
*/
private function logListingIfProject(Listing $listing): void
{
if ($listing->isProject()) {
$this->compoundStatusLogService->insert($listing);
}
}
/**
* Check existence of source and target listings.
*
* @param Listing|null $sourceListing
* @param Listing|null $targetListing
*/
private function isValidCriteriaForSyncListings($sourceListing, $targetListing): bool
{
return null !== $sourceListing && null !== $targetListing;
}
/**
* Check if saved time is greater than updated time.
*
* @param Listing|null $target
*/
private function targetListingUpdatedTimeGreaterThanSavedTime(int $savedAtTimeStamp, Listing $target): bool
{
$targetListingUpdateAt = $target->getUpdatedAt();
return $savedAtTimeStamp && $targetListingUpdateAt->getTimestamp() > $savedAtTimeStamp;
}
/**
* @param Listing|null $sourceListing
* @param Listing|null $targetListing
* @param int $targetSavedAtTimeStamp
*/
private function isReadyForSyncListingForProject($sourceListing, $targetListing, $targetSavedAtTimeStamp): bool
{
return $this->isValidCriteriaForSyncListings($sourceListing, $targetListing) && !$this->targetListingUpdatedTimeGreaterThanSavedTime($targetSavedAtTimeStamp, $targetListing);
}
/**
* @return Listing
*/
private function handleListingFeatureOnSave(Listing $listing)
{
/**
* @var ListingFeature $listingFeature
*/
if ($listingFeature = $listing->getCurrentListingFeature()) {
$listing->setFeatured($this->listingFeatureService->getListingFeaturedType($listingFeature->getType()));
}
return $listing;
}
/**
* @return bool
*/
private function isValidPhotoType($type)
{
if (\in_array($type, PhotoTypes::$photoTypes)) {
return true;
}
return false;
}
/**
* @return Listing
*/
private function handleUnlimitedListingOnRelist(Listing $listing)
{
$user = $listing->getUser();
if (
$this->userServicesManager->hasActiveService(UserServicesType::UNLIMITED_LISTINGS, $user)
&& $this->em->getRepository(\Aqarmap\Bundle\UserBundle\Entity\UserServices::class)->passUnlimitedListing($listing, $this->creditManager, $this)
) {
return $listing;
}
return $this->handleListingCategory($listing);
}
/**
* @return Listing
*/
private function handleListingCategory(Listing $listing)
{
$rules = $this->getListingRules($listing);
if ($rules['publication_fees'] > 0) {
$listing->setCategory(ListingCategories::PAID);
} else {
$listing->setCategory(ListingCategories::NORMAL);
}
return $listing;
}
/**
* @param array $listingPhones
*/
private function syncPhones(Listing $targetListing, Listing $sourceListing, $listingPhones): void
{
/** @var ListingPhone $listingPhone */
foreach ($listingPhones as $listingPhone) {
$listingPhoneRepo = $this->em->getRepository(ListingPhone::class);
$sourceListingPhone = $listingPhoneRepo->findOneBy([
'phone' => $listingPhone->getPhone(),
'listing' => $sourceListing,
]);
$targetListingPhone = $listingPhoneRepo->findOneBy([
'phone' => $listingPhone->getPhone(),
'listing' => $targetListing,
]);
if ($sourceListingPhone && !$targetListingPhone) {
$targetListingPhone = new ListingPhone();
$targetListingPhone
->setListing($targetListing)
->setPhone($listingPhone->getPhone());
$targetListing->addPhone($targetListingPhone);
} elseif (!$sourceListingPhone && $targetListingPhone) {
$targetListing->removePhone($targetListingPhone);
}
}
}
private function syncParticipants(Listing $targetListing, Listing $sourceListing, $participants): void
{
/** @var User $participant */
foreach ($participants as $participant) {
if ($sourceListing->hasParticipant($participant) && !$targetListing->hasParticipant($participant)) {
$targetListing->addParticipant($participant);
} elseif (!$sourceListing->hasParticipant($participant) && $targetListing->hasParticipant($participant)) {
$targetListing->removeParticipant($participant);
}
}
}
/**
* Dynamic set listing fields.
*
* @return $this
*/
private function updateFields(array $fields)
{
foreach ($fields as $field => $val) {
$fn = 'set'.ucwords($field);
if (method_exists($this->listing, $fn)) {
$this->listing->$fn($val);
}
}
return $this;
}
/**
* Dynamic set listing attributes.
*
* @return $this
*/
private function updateAttributes(array $fields)
{
foreach ($this->listing->getAttributes() as $attribute) {
$key = $attribute->getCustomField()->getName();
if (isset($fields['_attr'][$key])) {
$attribute->setValue($fields['_attr'][$key]);
$this->listing->addAttribute($attribute);
}
}
return $this;
}
/**
* Set listing phones.
*
* @param array $fields
*
* @return $this
*/
private function updatePhone($fields)
{
if (!isset($fields['_phone'])) {
return $this;
}
$phone = $fields['_phone'];
$entity = $this->em->getRepository(\Aqarmap\Bundle\ListingBundle\Entity\Phone::class)
->find($phone['id']);
$entity->setNumber($phone['number']);
$this->em->persist($entity);
$this->em->flush();
return $this;
}
/**
* Generate Translation For Listing Title.
*
* @param string $locale
*
* @return array
*/
private function generateTranslatedUnitTitle(Listing $listing, Listing $sourceListing, $locale)
{
$listingLocation = $listing->getLocation();
$listingPropertyType = $listing->getPropertyType();
$listingSection = $listing->getSection();
$listingArea = $listing->getArea();
$sourceListing->getTitle();
$sizeUnit = $this->settings->getSetting('general', 'measurement_unit');
$listingTitles = [];
if ($listingPropertyType && $listingSection && $listingArea && $sourceListing) {
$listingTitles[] = $this->translator->trans('layout.title.translations.unit_title', [
'%property_type%' => $listingPropertyType->getTitle(),
'%size%' => $listingArea.' '.$this->translator->trans($sizeUnit, [], null, $locale),
'%section%' => $listingSection->getTitle(),
'%in%' => $listingLocation ? $this->translator->trans('listing.in', [], null, $locale) : null,
'%parentListingTitle%' => $sourceListing->getTitle() ?: null,
'%parentListingAddress%' => $sourceListing->getAddress() ?: null,
], null, $locale);
}
return $listingTitles[array_rand($listingTitles)];
}
/**
* Generate Translation For Listing Title.
*
* @param string $locale
*
* @return array
*/
private function generateTranslatedListingTitle(Listing $listing, $locale)
{
$listingLocation = $listing->getLocation();
$listingPropertyType = $listing->getPropertyType();
$listingSection = $listing->getSection();
$listingArea = $listing->getArea();
$listingFinishType = $listing->getAttribute('finish-type');
$sizeUnit = $this->settings->getSetting('general', 'measurement_unit');
$listingTitles = [];
if ($listingPropertyType && $listingSection && $listingLocation) {
$listingTitles[] = $this->translator->trans('layout.title.translations.property_type_section_location', [
'%property_type%' => $listingPropertyType->getTitle(),
'%section%' => $listingSection->getTitle(),
'%in%' => $listingLocation ? $this->translator->trans('listing.in', [], null, $locale) : null,
'%location%' => $listingLocation ? $listingLocation->getTitle() : null,
], null, $locale);
}
if ($listingPropertyType && $listingSection && $listingLocation && $listingArea) {
$listingTitles[] = $this->translator->trans('layout.title.translations.property_type_size_section_location', [
'%property_type%' => $listingPropertyType->getTitle(),
'%size%' => $listingArea.' '.$this->translator->trans($sizeUnit, [], null, $locale),
'%section%' => $listingSection->getTitle(),
'%in%' => $listingLocation ? $this->translator->trans('listing.in', [], null, $locale) : null,
'%location%' => $listingLocation ? $listingLocation->getTitle() : null,
], null, $locale);
}
if ($listingPropertyType && $listingSection && $listingLocation && $listingFinishType) {
$listingTitles[] = $this->translator->trans('layout.title.translations.property_type_finish_section_location', [
'%property_type%' => $listingPropertyType->getTitle(),
'%finish%' => $this->translator->trans($listingFinishType->getValue(), [], null, $locale),
'%section%' => $listingSection->getTitle(),
'%in%' => $listingLocation ? $this->translator->trans('listing.in', [], null, $locale) : null,
'%location%' => $listingLocation ? $listingLocation->getTitle() : null,
], null, $locale);
}
if ($listingPropertyType && $listingLocation && $listingFinishType && $listingArea) {
$listingTitles[] = $this->translator->trans('layout.title.translations.property_type_size_finish_location', [
'%property_type%' => $listingPropertyType->getTitle(),
'%finish%' => $this->translator->trans($listingFinishType->getValue(), [], null, $locale),
'%size%' => $listingArea.' '.$this->translator->trans($sizeUnit, [], null, $locale),
'%in%' => $listingLocation ? $this->translator->trans('listing.in', [], null, $locale) : null,
'%location%' => $listingLocation ? $listingLocation->getTitle() : null,
], null, $locale);
}
if ($listingSection && $listingPropertyType && $listingLocation && $listing->getAttribute('finish-type') && $listing->getArea()) {
$listingTitles[] = $this->translator->trans('layout.title.translations.section_property_type_size_finish_location', [
'%section%' => $listingSection->getTitle(),
'%property_type%' => $listingPropertyType->getTitle(),
'%finish%' => $this->translator->trans($listingFinishType->getValue(), [], null, $locale),
'%size%' => $listingArea.' '.$this->translator->trans($sizeUnit, [], null, $locale),
'%in%' => $listingLocation ? $this->translator->trans('listing.in', [], null, $locale) : null,
'%location%' => $listingLocation ? $listingLocation->getTitle() : null,
], null, $locale);
}
if ($listingSection && $listingPropertyType && $listingFinishType && $listingArea) {
$listingTitles[] = $this->translator->trans('layout.title.translations.section_property_type_size_finish', [
'%section%' => $listingSection->getTitle(),
'%property_type%' => $listingPropertyType->getTitle(),
'%finish%' => $this->translator->trans($listing->getAttribute('finish-type')->getValue(), [], null, $locale),
'%size%' => $listingArea.' '.$this->translator->trans($sizeUnit, [], null, $locale),
], null, $locale);
}
if ($listingSection && $listingPropertyType && $listing->getAttribute('finish-type') && $listing->getArea()) {
$listingTitles[] = $this->translator->trans('layout.title.translations.property_type_location_size_finish_section', [
'%property_type%' => $listingPropertyType->getTitle(),
'%in%' => $listingLocation ? $this->translator->trans('listing.in', [], null, $locale) : null,
'%location%' => $listingLocation ? $listingLocation->getTitle() : null,
'%finish%' => $this->translator->trans($listingFinishType->getValue(), [], null, $locale),
'%size%' => $listingArea.' '.$this->translator->trans($sizeUnit, [], null, $locale),
'%section%' => $listingSection->getTitle(),
], null, $locale);
}
return $listingTitles[array_rand($listingTitles)];
}
/**
* @return float|int|null
*/
private function getListingNearestStatistics(Listing $listing)
{
$locationStatisticsRepository = $this->em->getRepository('AqarmapNeighborhoodBundle:LocationStatistics');
$listingLocation = $listing->getLocation();
$listingPricePerMeter = $listing->getPricePerMeter() ?: $listing->calculatePricePerMeter();
$listingValuation = null;
while ($listingLocation) {
/**
* @var LocationStatistics $locationStatistic
*/
$locationStatistic = $locationStatisticsRepository->findBy(['location' => $listingLocation]);
if ($this->locationStatisticsManager->isLocationStatisticReliable($locationStatistic)) {
$listingValuation = $this->calculateValuationDifference($listingPricePerMeter, $locationStatistic->getAvgPrice());
break;
}
$listingLocation = $listingLocation->getParent();
}
return $listingValuation;
}
/**
* @return float|int
*/
private function calculateValuationDifference($listingPricePerMeter, $statisticsPricePerMeter)
{
return min($listingPricePerMeter, $statisticsPricePerMeter) / max($listingPricePerMeter, $statisticsPricePerMeter) * 100;
}
/**
* @return array
*/
private function setFavouriteListings(array $listings, array $criteria)
{
$listingFavouriteIds = $this
->em
->getRepository(Favourite::class)
->findByIds($criteria)
->getArrayResult();
$hydratedListingFavouriteIds = array_map('current', $listingFavouriteIds);
foreach ($listings as $listing) {
if (\in_array($listing->getId(), $hydratedListingFavouriteIds)) {
$listing->setIsFavourite(true);
}
}
return $listings;
}
/**
* @return array
*/
private function setlistingNote(array $listings, array $criteria)
{
$listingNotes = $this
->em
->getRepository(\Aqarmap\Bundle\ListingBundle\Entity\ListingNote::class)
->findByIds($criteria)
->getArrayResult();
$flattenedListingNotes = array_column($listingNotes, 'note', 'listingId');
foreach ($listings as $listing) {
if (\array_key_exists($listing->getId(), $flattenedListingNotes)) {
$listing->setUserNote($flattenedListingNotes[$listing->getId()]);
}
}
return $listings;
}
/**
* Prepare Similar Listings Criteria.
*
* @return array
*/
private function prepareSimilarListingsCriteria(Listing $listing)
{
$areaRange = !empty($listing->getArea()) ? $this->getRangeOfPercentageOfValue($listing->getArea()) : [];
$priceRange = !empty($listing->getPrice()) ? $this->getRangeOfPercentageOfValue((int) $listing->getPrice()) : [];
return $criteria = [
'section' => $listing->getSection(),
'propertyType' => $listing->getPropertyType(),
'paymentMethod' => $listing->getPaymentMethod(),
'minArea' => $areaRange['min'] ?? 0,
'maxArea' => $areaRange['max'] ?? 0,
'minPrice' => $priceRange['min'] ?? 0,
'maxPrice' => $priceRange['max'] ?? 0,
'location' => $listing->getLocation(),
'excludedListing' => $listing,
];
}
/**
* @return string|null
*/
private function getStaticNoteByListingId(int $listingId, array $listingsWithNotes)
{
return $listingsWithNotes[array_search($listingId, array_column($listingsWithNotes, 'listingId'))]['note']
?? null;
}
/**
* @return int
*/
private function getDatesDifferenceInMonth(\DateTime $date)
{
return $date->diff(new \DateTime())->m;
}
private function isListingEmailIncludes(Listing $listing, string $partialEmail): bool
{
$email = $listing->getUser()->getEmailCanonical() ?? null;
if ($email && str_contains($email, $partialEmail)) {
return true;
}
return false;
}
/**
* @return UserManager
*/
private function getUserManagerService()
{
return $this->userManager;
}
/**
* @required
*/
public function setLeadService(LeadService $leadService): void
{
$this->leadService = $leadService;
}
/**
* @return array
*/
private function turnIntoUserToListingsMap(array $listingsCountPerUser)
{
$map = [];
foreach ($listingsCountPerUser as $item) {
$map[$item['userId']] = $item['liveListingsCount'];
}
return $map;
}
/**
* @required
*/
public function autowireCallRequestManager(CallRequestManager $callRequestManager): void
{
$this->callRequestManager = $callRequestManager;
}
/**
* @required
*/
public function setUserManagerService(UserManager $userManager): void
{
$this->userManager = $userManager;
}
/**
* @param array $criteria
*
* @return int
*/
private function getTotalImpressionsCount($criteria)
{
if ($criteria['startDate']) {
$listingIds = $this->listingRepository->getListingIds([
'user' => $criteria['user'],
'impressionUpdatedAt' => self::MAP_STRING_DAYS_WITH_NUMBER[$criteria['period']],
]);
$periodInDays = self::MAP_STRING_DAYS_WITH_NUMBER[$criteria['period']];
$totalImpressions = $this->listingImpressionRepository->countListingsTotalImpressions([
'listingIds' => array_flatten($listingIds),
'start_date' => date('Y-m-d h:i:s', strtotime(sprintf('-%d Days', $periodInDays))),
'end_date' => date('Y-m-d h:i:s'),
]);
} else {
$totalImpressions = $this->listingRepository->getListingsTotalImpressions([
'user' => $criteria['user'],
'column' => ImpressionTypes::getType($criteria['period']),
'listing_status' => 'ONLYLIVE' == $criteria['period'] ? ListingStatus::LIVE : null,
]);
}
return $totalImpressions;
}
/**
* @return Listing $listing
*/
private function handleProjectDates(Listing $listing)
{
$nextTenYearsDate = date(self::EXPIRATION_DATE_FORMAT, strtotime(self::ADD_TEN_YEARS));
$listing->setExpiresAt(new \DateTime(date(self::EXPIRATION_DATE_FORMAT, strtotime($nextTenYearsDate))));
if (null === $listing->getPublishedAt()) {
$listing->setPublishedAt(new \DateTime());
}
return $listing;
}
public function getListingsCount()
{
return $this->cache->get(self::LISTING_COUNTER_CACHE_KEY, function (ItemInterface $item) {
$item->expiresAfter(3600);
return $this->listingRepository->count(['status' => ListingStatus::LIVE]);
});
}
/**
* @description return true if user not has any live listing and has a valid free listing
*/
public function isEligibleForFreePublishing(Listing $listing): bool
{
$criteria = [
'user_id' => $listing->getUser()->getId(),
'status' => [
ListingStatus::DRAFT,
],
'reverseStatus' => true,
];
$listingCount = (int) $this->listingRepository->getListingsQuery($criteria)->getQuery()->getSingleScalarResult();
return $listingCount <= 1;
}
public function getListingMappedData(): ListingDataMapper
{
$mapped = new ListingDataMapper();
$mapped->setMapListingAttributes(true);
$mapped->setMapListingMainPhoto(true);
$mapped->setMapListingPhotos(true);
return $mapped;
}
public function getListingsElasticResponse(Request $request): array
{
$listingsIds = $this->searchClient->request(
'GET',
'/api/listing',
[
'query' => array_merge(
$request->query->all(),
['limit' => min($request->query->getInt('limit', 20), 100)]
),
]
);
return $listingsIds->toArray();
}
public function getRelatedListingsElasticResponse(Listing $listing): array
{
$listingsIds = $this->searchClient->request(
'GET',
sprintf('api/listing/%d/related-listings', $listing->getId())
);
return $listingsIds->toArray();
}
public function getListingsDebugElasticResponse(Request $request): array
{
$debugData = $this->searchClient->request(
'GET',
'/api/listing/debug',
[
'query' => $request->query->all(),
]
);
return $debugData->toArray();
}
public function getPropertiesChipsElasticResponse(Request $request): array
{
$request->query->set('aggregatedField', 'propertyType');
$propertyTypes = $this->searchClient->request(
'GET',
'/api/listing/histogram',
[
'query' => $request->query->all(),
]
);
return $propertyTypes->toArray();
}
public function bulkDeletePhotos(array $photos): void
{
foreach ($photos as $photo) {
$listing = $photo->getListing();
$listing->removePhoto($photo);
$this->em->persist($listing);
$this->em->flush();
$this->messageBus->dispatch(new ParentListingUpdated($photo));
}
}
/**
* @throws \Exception
*/
public function featuredAsSeller(Listing $listing, int $featuredType): array
{
$listingFeature = $this->listingFeatureService->getListingFeatured($featuredType);
$listingRule = $this->matcher->match($listing);
$user = $listing->getUser();
$fees = $this->listingFeatureService->getFeaturedTypeFees($listingRule, $listingFeature);
$duration = $this->listingFeatureService->getFeaturedTypeDuration($listingRule, $listingFeature);
if (empty($fees) || empty($duration) || !$this->listingFeatureService->isFeaturedOffersAvailable($listing)) {
return [
'type' => 'danger',
'message' => "No featured offers available for this listing ({$listing->getId()}) ",
];
}
if (!$this->isAffordable($user, $fees)) {
return [
'type' => 'danger',
'message' => "Not enough credit to make this listing ({$listing->getId()}) featured",
];
}
if ($this->isFeatured($listing)) {
return [
'type' => 'danger',
'message' => $this->translator->trans('listing.featured_failure_statement.already_featured', [
'%listingId%' => $listing->getId(),
]),
];
}
$this->makeItFeatured($listing, [
'featuredFees' => $fees,
'featuredDuration' => $duration,
'listingFeaturedType' => $featuredType,
'listingFeature' => $this->listingFeatureService->getListingFeatured($featuredType),
'featuredText' => $this->translator->trans(ListingFeaturedTypes::getFeaturedText($featuredType)),
]);
return [
'type' => 'success',
'message' => "Listing Id ({$listing->getId()}) has been set featured successfully",
];
}
/**
* @throws OptimisticLockException
* @throws ORMException
* @throws \Exception
*/
public function featuredAsAdmin(Listing $listing, int $featuredType): string
{
if ($featureData = $this->getFeaturingData($featuredType)) {
$this->activityLogger->record($featureData['activityType'], $listing->getId());
$message = $featureData['message'];
$listing->setFeatured($featuredType);
$this->dispatcher->dispatch(new ListingEvent($listing), $featureData['event']);
if (!empty($featureData['addFeatured'])) {
$this->AddFeaturedListingForFree($listing, $featureData['featureKey']);
}
} else {
$this->activityLogger->record(ActivityType::LISTING_UNFEATURED, $listing->getId());
$message = 'Listing has been set un featured';
$this->handleListingUnFeaturing($listing);
$this->dispatcher->dispatch(new ListingEvent($listing), 'aqarmap.listing.unfeatured');
}
$this->saveListing($listing);
return $message;
}
public function getFeaturingData($featuredType): ?array
{
switch ($featuredType) {
case ListingFeaturedTypes::FEATURED:
return [
'activityType' => ActivityType::LISTING_MAKE_FEATURED,
'message' => 'Listing has been set featured',
'event' => 'aqarmap.listing.featured',
'featureKey' => ListingFeatures::FEATURED_KEY,
'addFeatured' => true,
];
case ListingFeaturedTypes::SUPER_FEATURED:
return [
'activityType' => ActivityType::LISTING_MAKE_SUPER_FEATURED,
'message' => 'Listing has been set super featured',
'event' => 'aqarmap.listing.super_featured',
'addFeatured' => false,
];
case ListingFeaturedTypes::PREMIUM:
return [
'activityType' => ActivityType::LISTING_PREMIUM,
'message' => 'Listing has been set premium',
'event' => 'aqarmap.listing.premium',
'featureKey' => ListingFeatures::PREMIUM_KEY,
'addFeatured' => true,
];
case ListingFeaturedTypes::SPONSORED:
return [
'activityType' => ActivityType::LISTING_SPONSORED,
'message' => 'Listing has been set sponsored',
'event' => 'aqarmap.listing.sponsored',
'featureKey' => ListingFeatures::SPONSORED_KEY,
'addFeatured' => true,
];
case ListingFeaturedTypes::SPOTLIGHT:
return [
'activityType' => ActivityType::LISTING_SPOTLIGHT,
'message' => 'Listing has been set spotlight',
'event' => 'aqarmap.listing.spotlight',
'featureKey' => ListingFeatures::SPOTLIGHT_KEY,
'addFeatured' => true,
];
default:
return null;
}
}
public function addOrReplaceBrochure(Listing $listing, $brochure): Listing
{
$entityManager = $this->entityManager;
$oldFile = $listing->getBrochure();
$fileEntity = new File();
$fileEntity->setFile($brochure);
$listing->setBrochure($fileEntity);
if ($oldFile instanceof File) {
$entityManager->remove($oldFile);
}
return $listing;
}
public function translateListingDescriptionData($request, Listing $listing): Listing
{
foreach (Locales::getLocals() as $locale) {
$this->translatableListener->setTranslatableLocale($locale);
$this->em->refresh($listing);
$listing->setTitle($request->get('title-'.$locale));
$listing->setDescription($request->get('description-'.$locale));
$listing->setAddress($request->get('address-'.$locale));
$this->em->persist($listing);
$this->em->flush($listing);
}
$listing->setTranslatableLocale($request->getLocale());
return $listing;
}
public function removeBrochure(Listing $listing): Listing
{
$entityManager = $this->entityManager;
$file = $listing->getBrochure();
$listing->setBrochure(null);
if ($file instanceof File) {
$entityManager->remove($file);
}
return $listing;
}
}