src/Aqarmap/Bundle/ListingBundle/Service/InteractionService.php line 138

  1. <?php
  2. namespace Aqarmap\Bundle\ListingBundle\Service;
  3. use App\Message\Listing\ListingViewsCounterUpdatedMessage;
  4. use Aqarmap\Bundle\ListingBundle\Constant\InteractionTypes;
  5. use Aqarmap\Bundle\ListingBundle\Entity\Interaction;
  6. use Aqarmap\Bundle\ListingBundle\Entity\Listing;
  7. use Aqarmap\Bundle\ListingBundle\Repository\InteractionRepository;
  8. use Aqarmap\Bundle\MainBundle\Contract\ProducerFactoryInterface;
  9. use Aqarmap\Bundle\MainBundle\Service\MobileDetectionService;
  10. use Aqarmap\Bundle\UserBundle\Entity\User;
  11. use Aqarmap\Bundle\UserBundle\Services\UserInterestManager;
  12. use Aqarmap\Bundle\UserBundle\Services\UserManager;
  13. use Doctrine\ORM\EntityManagerInterface;
  14. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  15. use Symfony\Component\HttpFoundation\Cookie;
  16. use Symfony\Component\HttpFoundation\RequestStack;
  17. use Symfony\Component\HttpFoundation\Response;
  18. use Symfony\Component\Messenger\MessageBusInterface;
  19. use Symfony\Component\Security\Core\User\UserInterface;
  20. use Symfony\Contracts\Service\Attribute\Required;
  21. class InteractionService
  22. {
  23. private const CAMPAIGN_MAX_LENGTH = 50;
  24. /** @var RequestStack */
  25. private $request;
  26. /** @var ListingManager */
  27. private $listingManager;
  28. /**
  29. * @var EventDispatcherInterface
  30. */
  31. protected $dispatcher;
  32. /**
  33. * @var Storage
  34. */
  35. protected $tokenStorage;
  36. /**
  37. * @var ProducerFactoryInterface
  38. */
  39. protected $buyerAlertProducer;
  40. /**
  41. * @var UserInterestManager
  42. */
  43. protected $interestManager;
  44. /** @var InteractionRepository */
  45. protected $interactionRepository;
  46. /** @var ListingContactRateService */
  47. protected $listingContactRateService;
  48. public function __construct(
  49. private readonly EntityManagerInterface $em,
  50. RequestStack $request,
  51. EventDispatcherInterface $dispatcher,
  52. ProducerFactoryInterface $pokeProducerFactory,
  53. UserInterestManager $interestManager,
  54. protected UserManager $userManager,
  55. InteractionRepository $interactionRepository,
  56. ListingContactRateService $listingContactRateService,
  57. private readonly MobileDetectionService $mobileDetectionService,
  58. private MessageBusInterface $bus
  59. ) {
  60. $this->request = $request->getCurrentRequest();
  61. $this->dispatcher = $dispatcher;
  62. $this->interestManager = $interestManager;
  63. $this->interactionRepository = $interactionRepository;
  64. $this->listingContactRateService = $listingContactRateService;
  65. }
  66. /**
  67. * @param int $type
  68. * @param User $user
  69. * @param string|null $source
  70. * @param bool $flush
  71. */
  72. public function addInteraction(
  73. $type,
  74. Listing $listing,
  75. $user = null,
  76. $source = null,
  77. $flush = true,
  78. $campaign = null,
  79. ?string $userIp = null
  80. ): ?Interaction {
  81. if (InteractionTypes::LISTING_VIEWS === $type && $this->isBotRequest()) {
  82. return null;
  83. }
  84. $isValid = true;
  85. $resolvedIp = $userIp ?? $this->request?->getClientIp();
  86. $interaction = new Interaction();
  87. $interaction
  88. ->setUser($user)
  89. ->setListing($listing)
  90. ->setType($type)
  91. ->setUserIp($resolvedIp)
  92. ->setSource($source)
  93. ->setCampaign($this->truncateText($campaign, self::CAMPAIGN_MAX_LENGTH))
  94. ;
  95. if (\in_array($type, [InteractionTypes::LISTING_VIEWS])) {
  96. $isValid = $this->validateInteractionsDuplications($interaction);
  97. }
  98. if (true === $isValid) {
  99. $this->em->persist($interaction);
  100. if ($flush) {
  101. $this->em->flush([$interaction]);
  102. }
  103. $this->listingManager->incrementViewsCounters($listing);
  104. return $interaction;
  105. }
  106. return null;
  107. }
  108. /**
  109. * @return bool
  110. */
  111. private function validateInteractionsDuplications(Interaction $interaction)
  112. {
  113. $duplicates = $this->interactionRepository->checkDuplicateInteraction($interaction);
  114. return $duplicates ? false : true;
  115. }
  116. public function increaseViews(Listing $listing, ?UserInterface $user = null): void
  117. {
  118. if ($this->isBotRequest()) {
  119. return;
  120. }
  121. if (
  122. $user instanceof User
  123. && $listing->getUser()->getId() === $user->getId()
  124. ) {
  125. return;
  126. }
  127. $response = new Response();
  128. $views = [];
  129. if ($this->request->cookies->has('viewed_listings')) {
  130. $views = unserialize($this->request->cookies->get('viewed_listings'));
  131. if (\count($views) > 100) {
  132. array_pop($views);
  133. }
  134. }
  135. // Prevent duplication using cookies
  136. if (!\in_array($listing->getId(), $views)) {
  137. $views[] = $listing->getId();
  138. $response->headers->setCookie(Cookie::create('viewed_listings', serialize($views), time() + (86400 * 7)));
  139. $response->sendHeaders();
  140. $user = $user instanceof User ? $user : null;
  141. $this->bus->dispatch(
  142. new ListingViewsCounterUpdatedMessage(
  143. $listing->getId(),
  144. new \DateTimeImmutable(),
  145. $user?->getId(),
  146. $this->request->getClientIp(),
  147. 'interaction-service',
  148. $this->request->cookies->get('campaign')
  149. )
  150. );
  151. }
  152. }
  153. private function isBotRequest(): bool
  154. {
  155. $userAgent = $this->request?->headers->get('user-agent');
  156. return $this->mobileDetectionService->isBot($userAgent);
  157. }
  158. private function truncateText(?string $text, int $maxLength): ?string
  159. {
  160. if (null === $text) {
  161. return null;
  162. }
  163. return mb_substr($text, 0, $maxLength);
  164. }
  165. #[Required]
  166. public function setListingManager(ListingManager $listingManager): void
  167. {
  168. $this->listingManager = $listingManager;
  169. }
  170. }