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

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