src/Aqarmap/Bundle/ListingBundle/EventListener/ListingRuleListener.php line 183

  1. <?php
  2. namespace Aqarmap\Bundle\ListingBundle\EventListener;
  3. use App\Constant\LabelCode;
  4. use Aqarmap\Bundle\CreditBundle\Constant\CreditStatus;
  5. use Aqarmap\Bundle\CreditBundle\Contract\CreditManagerInterface;
  6. use Aqarmap\Bundle\CreditBundle\Entity\Credit;
  7. use Aqarmap\Bundle\ListingBundle\Constant\ListingCategories;
  8. use Aqarmap\Bundle\ListingBundle\Constant\ListingFeaturedStatus;
  9. use Aqarmap\Bundle\ListingBundle\Constant\ListingSource;
  10. use Aqarmap\Bundle\ListingBundle\Constant\ListingStatus;
  11. use Aqarmap\Bundle\ListingBundle\Entity\Listing;
  12. use Aqarmap\Bundle\ListingBundle\Event\ListingEvent;
  13. use Aqarmap\Bundle\ListingBundle\Event\ListingFeatureEvent;
  14. use Aqarmap\Bundle\ListingBundle\Service\Contracts\ListingFeatureServiceInterface;
  15. use Aqarmap\Bundle\ListingBundle\Service\ListingFeatureService;
  16. use Aqarmap\Bundle\ListingBundle\Service\ListingManager;
  17. use Aqarmap\Bundle\ListingBundle\Service\ListingRuleMatcher;
  18. use Aqarmap\Bundle\ListingBundle\Service\SpecialAddListingService;
  19. use Aqarmap\Bundle\UserBundle\Constant\UserServicesType;
  20. use Aqarmap\Bundle\UserBundle\Services\UserServicesManager;
  21. use Doctrine\ORM\EntityManagerInterface;
  22. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  23. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  24. use Symfony\Component\HttpFoundation\RedirectResponse;
  25. use Symfony\Component\HttpFoundation\RequestStack;
  26. use Symfony\Component\Routing\RouterInterface;
  27. use Symfony\Contracts\Translation\TranslatorInterface;
  28. class ListingRuleListener implements EventSubscriberInterface
  29. {
  30. public function __construct(private readonly ListingRuleMatcher $listingRuleMatcher, private readonly CreditManagerInterface $creditManager, private readonly ListingManager $listingManager, private readonly EntityManagerInterface $entityManager, private readonly ListingFeatureServiceInterface $listingFeatureService, private readonly RequestStack $requestStack, private readonly TranslatorInterface $translator, private readonly RouterInterface $router, private readonly SpecialAddListingService $specialAddListingService, private readonly UserServicesManager $userServicesManager, private readonly EventDispatcherInterface $dispatcher)
  31. {
  32. }
  33. public function preSaveListingEvent(ListingEvent $event): void
  34. {
  35. $listing = $event->getListing();
  36. if (
  37. !$listing->isDraft()
  38. && !$listing->isLive()
  39. && ListingCategories::FIRST_LISTING_FOR_FREE === $listing->getCategory()
  40. ) {
  41. $this->getListingManager()->changeStatus($listing, ListingStatus::PENDING, false);
  42. return;
  43. }
  44. $publicationCredit = $listing->getPublicationCredit();
  45. /** @var ListingRuleMatcher $matcher */
  46. $matcher = $this->listingRuleMatcher;
  47. $listingRule = $matcher->match($listing);
  48. $listingRulesRequirePhotos = ($listingRule['required_photos'] && $listing->getPhotos()->count() < $listingRule['required_photos']);
  49. $listingHasTitle = $listing->getTitle();
  50. if ($listingRulesRequirePhotos && $listingHasTitle) {
  51. $this->getListingManager()->changeStatus($listing, ListingStatus::PENDING_PHOTOS);
  52. }
  53. if (
  54. !$listing->isDraft()
  55. && !$listing->isLive()
  56. && !$listing->isProjectOrUnit()
  57. && ListingStatus::PENDING != $listing->getStatus()
  58. && ListingStatus::PENDING_PAYMENT != $listing->getStatus()
  59. && $listing->getPhotos()->count() >= $listingRule['required_photos']
  60. ) {
  61. $this->getListingManager()->changeStatus($listing, ListingStatus::PENDING_PAYMENT, false);
  62. }
  63. // If listing is PENDING_PAYMENT, the user already paid the fees
  64. if (ListingStatus::PENDING_PAYMENT === $listing->getStatus()) {
  65. if ($publicationCredit) {
  66. $this->getListingManager()->changeStatus($listing, ListingStatus::PENDING, false);
  67. }
  68. }
  69. // ----------------------------------------------------------------------
  70. // Toggle the listing's category on location change.
  71. // ----------------------------------------------------------------------
  72. $isPaid = ListingCategories::PAID === $listing->getCategory();
  73. $isScrapped = ListingCategories::SCRAPPED === $listing->getCategory();
  74. $isUnlimited = ListingCategories::UNLIMITED === $listing->getCategory();
  75. if (!$isPaid && !$isScrapped && !$isUnlimited && $listingRule['publication_fees'] > 0) {
  76. $listing->setCategory(ListingCategories::PAID);
  77. if (ListingStatus::PENDING === $listing->getStatus() && !$publicationCredit) {
  78. $this->getListingManager()->changeStatus($listing, ListingStatus::PENDING_PAYMENT, false);
  79. }
  80. }
  81. if (
  82. !$isPaid && !$isScrapped && !$isUnlimited && $listingHasTitle && $listingRule['publication_fees'] <= 0
  83. && \in_array($listing->getStatus(), [ListingStatus::PENDING_PAYMENT, ListingStatus::DRAFT])
  84. ) {
  85. $this->getListingManager()->changeStatus($listing, ListingStatus::PENDING, false);
  86. }
  87. if ($isPaid && !$isScrapped && !$isUnlimited && $listingRule['publication_fees'] <= 0) {
  88. $listing->setCategory(ListingCategories::NORMAL);
  89. if (ListingStatus::PENDING_PAYMENT === $listing->getStatus()) {
  90. $this->getListingManager()->changeStatus($listing, ListingStatus::PENDING_PAYMENT, false);
  91. }
  92. }
  93. if ($this->getListingManager()->isListingUserEbawab($listing)) {
  94. $listing->setCategory(ListingCategories::EBAWAB);
  95. }
  96. if ($this->getListingManager()->isListingUserManualScraped($listing)) {
  97. $listing->setCategory(ListingCategories::SCRAPPED);
  98. $this->listingManager->addLabelByCode($listing, LabelCode::LISTING_SCRAPPED);
  99. }
  100. }
  101. public function onSubmittedEvent(ListingEvent $event): void
  102. {
  103. /** @var Listing $listing */
  104. $listing = $event->getListing();
  105. $matcher = $this->listingRuleMatcher;
  106. $listingRule = $matcher->match($listing);
  107. if (!$listingRule['flf2'] && ListingCategories::PAID === $listing->getCategory() && $this->listingManager->isEligibleForFreePublishing($listing)) {
  108. $this->listingManager->createFirstListingForFree($listing);
  109. }
  110. if (
  111. ListingSource::LITE == $listing->getSource()
  112. || ListingSource::SCRAPPING == $listing->getSource()
  113. || ListingCategories::FIRST_LISTING_FOR_FREE === $listing->getCategory()
  114. ) {
  115. return;
  116. }
  117. if ($listingRule['publication_fees'] > 0 && $listing->getPhotos()->count() >= $listingRule['required_photos']) {
  118. if ((
  119. $this->specialAddListingService->hasSpecialAddListingGroup($listing->getUser())
  120. && !$listing->getSpecialPublicationCredit()
  121. )
  122. || (
  123. !$this->specialAddListingService->hasSpecialAddListingGroup($listing->getUser())
  124. && !$listing->getPublicationCredit()
  125. )
  126. ) {
  127. $this->getListingManager()->changeStatus($listing, ListingStatus::PENDING_PAYMENT, true, true);
  128. }
  129. }
  130. }
  131. public function onSubmittedStatusRedirectionEvent(ListingEvent $event): void
  132. {
  133. /** @var Listing $listing */
  134. $listing = $event->getListing();
  135. $userId = $listing->getUser();
  136. if ($this->specialAddListingService->hasSpecialAddListingGroup($userId)) {
  137. $userUnlimitedListings = $this->userServicesManager->hasActiveService(UserServicesType::UNLIMITED_LISTINGS, $userId);
  138. if (!$userUnlimitedListings || ($userUnlimitedListings && 0 == $userUnlimitedListings['remainingQuota'])) {
  139. $event->setResponse($this->redirect(
  140. 'listing_slug',
  141. ['id' => $listing->getId(), 'slug' => $listing->getSlug()]
  142. ));
  143. return;
  144. }
  145. }
  146. switch ($listing->getStatus()) {
  147. case ListingStatus::PENDING_PAYMENT:
  148. $event->setResponse($this->redirect(
  149. 'listing_confirm_publish_credit',
  150. ['id' => $listing->getId()]
  151. ));
  152. break;
  153. case ListingStatus::PENDING_PHOTOS:
  154. $this->setFlashMessage('info', $this->getTranslator()->trans('add_listing_page.success_messages.add_photos_message'));
  155. $event->setResponse($this->redirect(
  156. 'listing_upload',
  157. ['id' => $event->getListing()->getId()]
  158. ));
  159. break;
  160. }
  161. }
  162. /**
  163. * @throws \Exception
  164. */
  165. public function onListingPublishEvent(ListingEvent $event): void
  166. {
  167. $listing = $event->getListing();
  168. $listingPublishPaid = $listing->getPublicationCredit();
  169. $listingPublishFeatured = $listing->getFeaturedPublicationCredit();
  170. $listingRule = $this->listingRuleMatcher->match($listing);
  171. if (ListingCategories::FIRST_LISTING_FOR_FREE === $listing->getCategory()) {
  172. $listing->setExpiresAt(new \DateTime('+'.$listingRule['first_free_duration'].' days'));
  173. return;
  174. }
  175. if ($listingRule['duration'] && (!$listing->getExpiresAt() || new \DateTime() >= $listing->getExpiresAt())) {
  176. $listing->setExpiresAt(new \DateTime('+'.$listingRule['duration'].' days'));
  177. }
  178. if ($listingPublishPaid) {
  179. if (!$listingPublishPaid->getExpiresAt()) {
  180. $listingPublishPaid->setExpiresAt($listing->getExpiresAt());
  181. }
  182. }
  183. if ($listingPublishFeatured) {
  184. if (!$listingPublishFeatured->getExpiresAt() && !$listingPublishFeatured->getFeaturingStatus()) {
  185. $duration = $this->getListingFeaturingService()->getFeaturedTypeDuration($listingRule, $listingPublishFeatured->getType());
  186. $listingPublishFeatured->setExpiresAt(new \DateTime('+'.$duration.' days'));
  187. $this->dispatcher->dispatch(new ListingFeatureEvent($listingPublishFeatured), 'aqarmap.listing.feature.bumpup');
  188. }
  189. }
  190. }
  191. public function onFeaturingListingApprovalEvent(ListingEvent $event): void
  192. {
  193. $listing = $event->getListing();
  194. $listingPublishFeatured = $listing->getFeaturedPublicationCredit();
  195. $listingManager = $this->getListingManager();
  196. $listingFeatureService = $this->getListingFeaturingService();
  197. $listingRule = $listingManager->getListingRules($listing);
  198. if ($listingPublishFeatured && ListingStatus::LIVE == $listing->getStatus()) {
  199. $listing->setFeatured($listingFeatureService->getListingFeaturedType($listingPublishFeatured->getType()));
  200. $listingPublishFeatured->setFeaturingStatus(ListingFeaturedStatus::APPROVED);
  201. $listingPublishFeatured->setApprovedAt(new \DateTime());
  202. if (!$listingPublishFeatured->getExpiresAt()) {
  203. $duration = $this->getListingFeaturingService()->getFeaturedTypeDuration($listingRule, $listingPublishFeatured->getType());
  204. $listingPublishFeatured->setExpiresAt(new \DateTime('+'.$duration.' days'));
  205. }
  206. }
  207. $em = $this->getEntityManager();
  208. $em->persist($listing);
  209. $em->flush();
  210. }
  211. /**
  212. * @throws \Exception
  213. */
  214. public function onFeaturingListingRejectEvent(ListingEvent $event): void
  215. {
  216. $listing = $event->getListing();
  217. $listingFeatured = $listing->getLastListingFeature();
  218. if (ListingStatus::LIVE == $listing->getStatus()) {
  219. $listingFeatured->setFeaturingStatus(ListingFeaturedStatus::REJECTED);
  220. $listingFeatured->setExpiresAt(new \DateTime('-2 days'));
  221. }
  222. $em = $this->getEntityManager();
  223. $em->persist($listingFeatured);
  224. $featureTypeLabel = $this->getTranslator()->trans($listingFeatured->getPendingFeaturedLabel());
  225. $balance = $this->creditManager->getBalance($listing->getUser());
  226. $amount = abs($listingFeatured->getCredit()->getAmount());
  227. $credit = new Credit();
  228. $credit
  229. ->setUser($listing->getUser())
  230. ->setAmount(abs($listingFeatured->getCredit()->getAmount()))
  231. ->setDescription('Rejected to be '.$featureTypeLabel)
  232. ->setBalance($balance + $amount)
  233. ->setStatus(CreditStatus::SUCCESS)
  234. ->setCreatedAt(new \DateTime());
  235. $em->persist($credit);
  236. $em->flush();
  237. }
  238. public function getEntityManager()
  239. {
  240. return $this->entityManager;
  241. }
  242. /**
  243. * @return TranslatorInterface
  244. */
  245. public function getTranslator()
  246. {
  247. return $this->translator;
  248. }
  249. public static function getSubscribedEvents(): array
  250. {
  251. return [
  252. 'aqarmap.listing.pre_save' => ['preSaveListingEvent'],
  253. 'aqarmap.listing.submitted' => [['onSubmittedEvent'], ['onSubmittedStatusRedirectionEvent']],
  254. 'aqarmap.listing.resubmitted' => [['onSubmittedEvent'], ['onSubmittedStatusRedirectionEvent']],
  255. 'aqarmap.listing.publish' => ['onListingPublishEvent'],
  256. 'aqarmap.listing.featuring.approved' => ['onFeaturingListingApprovalEvent'],
  257. 'aqarmap.listing.free_publish' => ['onListingPublishEvent'],
  258. 'aqarmap.listing.publish_without_photos' => ['onListingPublishEvent'],
  259. 'aqarmap.listing.featuring.rejected' => ['onFeaturingListingRejectEvent'],
  260. ];
  261. }
  262. /**
  263. * @return ListingManager
  264. */
  265. protected function getListingManager()
  266. {
  267. return $this->listingManager;
  268. }
  269. /**
  270. * @return ListingFeatureService
  271. */
  272. protected function getListingFeaturingService()
  273. {
  274. return $this->listingFeatureService;
  275. }
  276. /**
  277. * @param string $type
  278. * @param string $message
  279. */
  280. private function setFlashMessage($type, $message): void
  281. {
  282. if (($req = $this->requestStack->getCurrentRequest()) && $req->hasPreviousSession()) {
  283. $req->getSession()->getFlashBag()->add($type, $message);
  284. }
  285. }
  286. private function redirect($route, $parameters = [])
  287. {
  288. return new RedirectResponse(
  289. $this->router->generate($route, $parameters),
  290. \Symfony\Component\HttpFoundation\Response::HTTP_FOUND
  291. );
  292. }
  293. }