<?php
namespace Aqarmap\Bundle\ListingBundle\EventListener;
use App\Message\Listing\AfterChangeStatusMessage;
use Aqarmap\Bundle\CreditBundle\Constant\CreditStatus;
use Aqarmap\Bundle\CreditBundle\Contract\CreditManagerInterface;
use Aqarmap\Bundle\CreditBundle\Entity\Credit;
use Aqarmap\Bundle\ListingBundle\Constant\ListingActivityType;
use Aqarmap\Bundle\ListingBundle\Constant\ListingCategories;
use Aqarmap\Bundle\ListingBundle\Constant\ListingFeatures;
use Aqarmap\Bundle\ListingBundle\Constant\ListingStatus;
use Aqarmap\Bundle\ListingBundle\Document\ListingActivityLog;
use Aqarmap\Bundle\ListingBundle\Entity\Listing;
use Aqarmap\Bundle\ListingBundle\Event\ListingEvent;
use Aqarmap\Bundle\ListingBundle\Message\ListingDeleted;
use Aqarmap\Bundle\ListingBundle\Service\ListingManager;
use Aqarmap\Bundle\ListingBundle\Service\ListingRuleMatcher;
use Aqarmap\Bundle\ListingBundle\Service\SpecialAddListingService;
use Aqarmap\Bundle\MainBundle\Adapter\MailerServiceInterface;
use Aqarmap\Bundle\MainBundle\Constant\ActivityType;
use Aqarmap\Bundle\MainBundle\Constant\Locales;
use Aqarmap\Bundle\MainBundle\Contract\ProducerFactoryInterface;
use Aqarmap\Bundle\MainBundle\Helpers\MailerHelper;
use Aqarmap\Bundle\MainBundle\Service\ActivityLogger;
use Aqarmap\Bundle\NotificationBundle\Types\ListingHasExpired;
use Aqarmap\Bundle\UserBundle\Constant\UserTypes;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Twig\Environment;
class ListingStatusListener implements EventSubscriberInterface
{
/**
* @var MailerServiceInterface
*/
protected $mailer;
/**
* @var ProducerFactoryInterface
*/
private $pokeProducerFactory;
/**
* @var EntityManagerInterface
*/
private $entityManager;
/**
* @var TokenStorageInterface
*/
private $tokenStorage;
/**
* @var ListingRuleMatcher
*/
private $listingRuleMatcher;
/**
* @var CreditManagerInterface
*/
private $creditManager;
/**
* @var ActivityLogger
*/
private $activityLogger;
/**
* @var EventDispatcherInterface
*/
private $eventDispatcher;
/**
* @var TranslatorInterface
*/
private $translator;
/**
* @var ListingManager
*/
private $listingManager;
/**
* @var EngineInterface
*/
private $engine;
/**
* @var MailerHelper
*/
private $mailerHelper;
/** @var ParameterBagInterface */
private $parameterBag;
private MessageBusInterface $messageBus;
/**
* @var DocumentManager
*/
private $documentManager;
/**
* Constructor.
*/
public function __construct(
ProducerFactoryInterface $pokeProducerFactory,
MailerServiceInterface $mailer,
EntityManagerInterface $entityManager,
TokenStorageInterface $tokenStorage,
ListingRuleMatcher $listingRuleMatcher,
CreditManagerInterface $creditManager,
ActivityLogger $activityLogger,
EventDispatcherInterface $eventDispatcher,
TranslatorInterface $translator,
ListingManager $listingManager,
SessionInterface $session,
RouterInterface $router,
Environment $engine,
MailerHelper $mailerHelper,
ParameterBagInterface $parameterBag,
SpecialAddListingService $specialAddListingService,
MessageBusInterface $messageBus,
DocumentManager $documentManager
) {
$this->pokeProducerFactory = $pokeProducerFactory;
$this->mailer = $mailer;
$this->entityManager = $entityManager;
$this->tokenStorage = $tokenStorage;
$this->listingRuleMatcher = $listingRuleMatcher;
$this->creditManager = $creditManager;
$this->activityLogger = $activityLogger;
$this->eventDispatcher = $eventDispatcher;
$this->translator = $translator;
$this->listingManager = $listingManager;
$this->engine = $engine;
$this->mailerHelper = $mailerHelper;
$this->parameterBag = $parameterBag;
$this->specialAddListingService = $specialAddListingService;
$this->messageBus = $messageBus;
$this->documentManager = $documentManager;
}
public function preSaveListingEvent(ListingEvent $event): void
{
$listing = $event->getListing();
if (null === $listing->getStatus()) {
$this->getListingManager()->changeStatus($listing, ListingStatus::DRAFT, false);
}
}
public function onSubmittedEvent(ListingEvent $event): void
{
$listing = $event->getListing();
// Add or edit status
$isDraftOrLive = \in_array($listing->getStatus(), [
ListingStatus::DRAFT,
ListingStatus::LIVE,
ListingStatus::REJECTED,
]);
// IF listing owner has admin role && listing status is
// DRAFT (add) Change listing status to LIVE
if ($listing->getUser()->hasRole('ROLE_ADMIN') && ListingStatus::DRAFT === $listing->getStatus()) {
$this->getListingManager()->changeStatus($listing, ListingStatus::LIVE, true);
}
if (ListingCategories::FIRST_LISTING_FOR_FREE == $listing->getCategory()
|| ($isDraftOrLive && !$listing->getUser()->hasRole('ROLE_ADMIN'))
) {
$this->getListingManager()->changeStatus($listing, ListingStatus::PENDING, true);
}
if ($this->specialAddListingService->hasSpecialAddListingGroup($listing->getUser())
&& ListingStatus::PENDING_PAYMENT === $listing->getStatus()
&& $listing->getSpecialPublicationCredit()
) {
$this->getListingManager()->changeStatus($listing, ListingStatus::PENDING, true);
}
}
public function onListingPublishEvent(ListingEvent $event): void
{
$listing = $event->getListing();
$user = $listing->getUser();
// unsetting pending payment and photos statuses
$listing->setPendingPaymentStatus(null);
$listing->setPendingPhotosStatus(null);
// If no publish did already or the listing is expired and being relisted give it a bump
if (!$listing->getPublishedAt() || ListingStatus::EXPIRED == $listing->getStatus()) {
$listing->setPublishedAt(new \DateTime());
}
$em = $this->entityManager;
$userListingsCount = $em->getRepository(Listing::class)
->getUserListingsCountByStatus($user, [ListingStatus::LIVE]);
$token = $this->tokenStorage->getToken();
// Only flag if user has more than 3 listings and he is not already flagged
if ($token
&& !$user->hasRole('ROLE_IN_HOUSE')
&& $userListingsCount >= 2
&& UserTypes::INDIVIDUAL == $user->getUserType()
) {
$user->setUserType(UserTypes::BROKER);
$em->persist($user);
$em->flush($user);
}
$listing->addRejections(new ArrayCollection());
}
public function onListingPublishForFreeEvent(ListingEvent $event): void
{
$listing = $event->getListing();
$listingRules = $this->listingRuleMatcher->match($listing);
if ($listingRules['publication_fees']) {
$creditManager = $this->creditManager;
$credits = $creditManager->deduction($listing->getUser(), (float) 0, 'Free publishing', CreditStatus::SUCCESS);
foreach ($credits as $credit) {
if ($credit instanceof Credit) {
$this->getListingManager()->addFeature($listing, ListingFeatures::PAID, null, $credit);
}
}
// Record this acction in the activity logger
if (ListingCategories::SCRAPPED != $listing->getCategory()) {
$this->activityLogger->record(ActivityType::LISTING_FREE_PUBLISH, $listing->getId());
}
}
}
public function onPublishedForFreeEmailEvent(ListingEvent $event): void
{
$listing = $event->getListing();
$originalLocale = $this->getTranslator()->getLocale();
$this->getTranslator()->setLocale($this->getListingLanguage($listing));
$template = '@AqarmapListingBundle/Admin/Email/listingApprovedForFree.html.twig';
$templateContext = ['listing' => $listing];
$compose = $this->getComposeMessage($listing, $template, $templateContext, 'listings-free-publish');
$this->getMailer()->sendMessage($compose);
$this->getTranslator()->setLocale($originalLocale);
}
/**
* Unset DeletedAt date on undelete event.
*/
public function onListingUndeleteEvent(ListingEvent $event): void
{
$listing = $event->getListing();
$listing->setDeletedAt(null);
}
/**
* Refund listing credit on delete event.
*/
public function onListingDeleteEvent(ListingEvent $event): void
{
$listing = $event->getListing();
$listingFeatured = $listing->getNotExpiredCredit();
/*
Refund Policy,
listings should have paid features that are not expired yet,
Also the listing should never been published before.
*/
if (!empty($listingFeatured) && !$listing->getPublishedAt()) {
foreach ((array) $listingFeatured as $listingFees) {
$credits[] = $listingFees->getCredit();
}
$creditManager = $this->creditManager;
$em = $this->getEntityManager();
foreach ($credits as $credit) {
if ($credit->canBeCancelled()) {
$creditManager->deposit($credit->getUser(), -$credit->getAmount(), CreditStatus::SUCCESS, CreditStatus::REFUND_LABEL);
$credit->setStatus(CreditStatus::REFUND);
$em->persist($credit);
}
}
$em->flush();
}
$listing->setDeletedAt(new \DateTime());
}
/**
* Refund listing credit on delete event.
*/
public function onListingStatusDeleteEvent(ListingEvent $event): void
{
$this->messageBus->dispatch(new ListingDeleted($event->getListing()));
}
public function onApprovedSendEmailEvent(ListingEvent $event): void
{
$listing = $event->getListing();
$originalLocale = $this->getTranslator()->getLocale();
$this->getTranslator()->setLocale($this->getListingLanguage($listing));
$template = '@AqarmapListingBundle/Admin/Email/listingApproved.html.twig';
$templateContext = ['listing' => $listing];
$compose = $this->getComposeMessage($listing, $template, $templateContext, 'listings-approval');
$this->getMailer()->sendMessage($compose);
$this->getTranslator()->setLocale($originalLocale);
}
public function onListingExpiredEvent(ListingEvent $event): void
{
$listing = $event->getListing();
$this->eventDispatcher
->dispatch((new ListingHasExpired())->setSubject($listing), 'listing.has.expired');
}
public function onListingPhotosDeleted(ListingEvent $event): void
{
$listing = $event->getListing();
$this->getListingManager()->changeStatus($listing, ListingStatus::PENDING_PHOTOS, true);
}
/**
* @return object
*/
public function getEntityManager()
{
return $this->entityManager;
}
/**
* @return MailerServiceInterface
*/
public function getMailer()
{
return $this->mailer;
}
/**
* @return \Symfony\Component\Translation\LoggingTranslator
*/
public function getTranslator()
{
return $this->translator;
}
public function getTemplating()
{
return $this->engine;
}
public function changeListingWaitingTime(ListingEvent $event): void
{
$now = new \DateTime();
$listing = $event->getListing();
$activityRepo = $this->documentManager->getRepository(ListingActivityLog::class);
$lastActivityWaitingTime = $activityRepo->getLastActivity($listing->getId(), ListingActivityType::PENDING_APPROVAL);
$listing->setWaitingTime($lastActivityWaitingTime ? $now->getTimestamp() - $lastActivityWaitingTime->getCreatedAt()->getTimestamp() : null);
$this->entityManager->persist($listing);
$this->entityManager->flush();
}
public function onListingStatusPendingEvent(ListingEvent $event): void
{
$listing = $event->getListing();
if (!$listing->getPendingStatusCreatedAt() && !$event->getIsAdmin()) {
$listing->setPendingStatusCreatedAt(new \DateTime());
}
}
public function onListingStatusChangedEvent(ListingEvent $event): void
{
$listing = $event->getListing();
if ($listing) {
if (in_array($listing->getStatus(), [
ListingStatus::LIVE,
ListingStatus::EXPIRED,
ListingStatus::USER_DELETED,
ListingStatus::ADMIN_DELETED,
])) {
$this->messageBus->dispatch(new AfterChangeStatusMessage($listing->getId()));
}
}
}
public static function getSubscribedEvents()
{
return [
'aqarmap.listing.pre_save' => ['preSaveListingEvent'],
'aqarmap.listing.publish' => [
['onListingPublishEvent', 100], ['changeListingWaitingTime'],
],
'aqarmap.listing.submitted' => ['onSubmittedEvent', 100],
'aqarmap.listing.expired' => ['onListingExpiredEvent'],
'aqarmap.listing.undelete' => ['onListingUndeleteEvent'],
'aqarmap.listing.delete' => [['onListingStatusDeleteEvent'], ['onListingDeleteEvent']],
'aqarmap.listing.delete_by_user' => ['onListingStatusDeleteEvent'],
'aqarmap.listing.free_publish' => [
['onListingPublishForFreeEvent'], ['onListingPublishEvent'], ['onPublishedForFreeEmailEvent'],
],
'aqarmap.listing.publish_without_photos' => [
['onListingPublishForFreeEvent'], ['onListingPublishEvent'],
],
'aqarmap.listing.photos_deleted' => ['onListingPhotosDeleted'],
'aqarmap.listing.rejected' => ['changeListingWaitingTime'],
'aqarmap.listing.pending_approval' => ['onListingStatusPendingEvent'],
'aqarmap.listing.after_change_status' => ['onListingStatusChangedEvent'],
];
}
/**
* @return ListingManager
*/
protected function getListingManager()
{
return $this->listingManager;
}
private function getComposeMessage($listing, $template, $templateContext, $headerCategory)
{
$composeMessage = $this->mailerHelper->createMessageWithGlobalAttributes();
$composeMessage->setSubject($this->getTranslator()->trans('email.subject.listing_approved'));
$composeMessage->setTo($listing->getUser()->getEmailCanonical());
$composeMessage->setReplyTo($listing->getUser()->getEmailCanonical());
$composeMessage->setTemplate($template);
$composeMessage->setTemplateContext($templateContext);
$compose = $this->getMailer()->composeMessage($composeMessage);
$compose->getHeaders()->addTextHeader('X-Mail-Category', $headerCategory);
$compose->getHeaders()->addTextHeader('X-Site-Country', $this->parameterBag->get('country'));
return $compose;
}
private function getListingLanguage($listing)
{
$language = Locales::AR;
if (null !== $listing->getUser()->getLanguage()) {
$language = $listing->getUser()->getLanguage();
}
return $language;
}
}