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

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