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