src/Aqarmap/Bundle/ListingBundle/Controller/ListingController.php line 467

  1. <?php
  2. namespace Aqarmap\Bundle\ListingBundle\Controller;
  3. use App\Exception\LogicHttpException;
  4. use Aqarmap\Bundle\CreditBundle\Constant\CreditStatus;
  5. use Aqarmap\Bundle\CreditBundle\Entity\Credit;
  6. use Aqarmap\Bundle\CreditBundle\Services\CreditManager;
  7. use Aqarmap\Bundle\FeatureToggleBundle\Service\FeatureToggleManager;
  8. use Aqarmap\Bundle\FinancialAidsBundle\Service\FinancialAidService;
  9. use Aqarmap\Bundle\ListingBundle\Constant\LeadTypes;
  10. use Aqarmap\Bundle\ListingBundle\Constant\ListingCategories;
  11. use Aqarmap\Bundle\ListingBundle\Constant\ListingFeaturedTypes;
  12. use Aqarmap\Bundle\ListingBundle\Constant\ListingFeatures;
  13. use Aqarmap\Bundle\ListingBundle\Constant\ListingSections;
  14. use Aqarmap\Bundle\ListingBundle\Constant\ListingSource;
  15. use Aqarmap\Bundle\ListingBundle\Constant\ListingStatus;
  16. use Aqarmap\Bundle\ListingBundle\Constant\PropertyRegistrationStatusOption;
  17. use Aqarmap\Bundle\ListingBundle\Constant\ResaleListingsConstant;
  18. use Aqarmap\Bundle\ListingBundle\Contracts\PhoneManagerInterface;
  19. use Aqarmap\Bundle\ListingBundle\Contracts\RelatedResultServiceInterface;
  20. use Aqarmap\Bundle\ListingBundle\Entity\CallRequest;
  21. use Aqarmap\Bundle\ListingBundle\Entity\File;
  22. use Aqarmap\Bundle\ListingBundle\Entity\Listing;
  23. use Aqarmap\Bundle\ListingBundle\Entity\ListingPhone;
  24. use Aqarmap\Bundle\ListingBundle\Entity\Phone;
  25. use Aqarmap\Bundle\ListingBundle\Event\ListingEvent;
  26. use Aqarmap\Bundle\ListingBundle\Exception\InvalidLeadValidationHttpException;
  27. use Aqarmap\Bundle\ListingBundle\Form\CallRequestFormType;
  28. use Aqarmap\Bundle\ListingBundle\Form\ContactSellerFormType;
  29. use Aqarmap\Bundle\ListingBundle\Form\ContactSellerWideFormType;
  30. use Aqarmap\Bundle\ListingBundle\Form\ListingInitializeType;
  31. use Aqarmap\Bundle\ListingBundle\Form\ListingType;
  32. use Aqarmap\Bundle\ListingBundle\Form\PhotoType;
  33. use Aqarmap\Bundle\ListingBundle\Form\QuickLeadType;
  34. use Aqarmap\Bundle\ListingBundle\Repository\ListingPhotoRepository;
  35. use Aqarmap\Bundle\ListingBundle\Service\CallRequestManager;
  36. use Aqarmap\Bundle\ListingBundle\Service\Contracts\ListingLeadManagerInterface;
  37. use Aqarmap\Bundle\ListingBundle\Service\Contracts\LocationManagerInterface;
  38. use Aqarmap\Bundle\ListingBundle\Service\FreeListingService;
  39. use Aqarmap\Bundle\ListingBundle\Service\InteractionService;
  40. use Aqarmap\Bundle\ListingBundle\Service\ListingFeatureService;
  41. use Aqarmap\Bundle\ListingBundle\Service\ListingManager;
  42. use Aqarmap\Bundle\ListingBundle\Service\ListingRuleMatcher;
  43. use Aqarmap\Bundle\ListingBundle\Service\Mortgage\MortgageService;
  44. use Aqarmap\Bundle\ListingBundle\Service\SpecialAddListingService;
  45. use Aqarmap\Bundle\ListingBundle\Service\V4\CompoundDetailService;
  46. use Aqarmap\Bundle\MainBundle\Form\ConfirmFeaturedFormType;
  47. use Aqarmap\Bundle\MainBundle\Form\ConfirmFormType;
  48. use Aqarmap\Bundle\MainBundle\Service\MobileDetectionService;
  49. use Aqarmap\Bundle\MainBundle\Service\Setting;
  50. use Aqarmap\Bundle\MessageBundle\Service\Composer;
  51. use Aqarmap\Bundle\NeighborhoodBundle\Service\NeighborhoodManager;
  52. use Aqarmap\Bundle\NotificationBundle\DatabaseNotification;
  53. use Aqarmap\Bundle\NotifierBundle\Event\NotifierEvent;
  54. use Aqarmap\Bundle\OTPBundle\Contract\OtpServiceInterface;
  55. use Aqarmap\Bundle\SearchBundle\Services\CompoundFaqsService;
  56. use Aqarmap\Bundle\SearchBundle\Services\ElasticListingSearch\DefaultListingSearch;
  57. use Aqarmap\Bundle\TopSellerBundle\Model\TopSeller;
  58. use Aqarmap\Bundle\TopSellerBundle\Service\TopSellerRetrievalService;
  59. use Aqarmap\Bundle\UserBundle\Constant\UserServicesType;
  60. use Aqarmap\Bundle\UserBundle\Constant\UserTypes;
  61. use Aqarmap\Bundle\UserBundle\Entity\User;
  62. use Aqarmap\Bundle\UserBundle\Entity\UserServices;
  63. use Aqarmap\Bundle\UserBundle\Form\QuickRegistrationFormType;
  64. use Aqarmap\Bundle\UserBundle\Repository\UserPackagesRepository;
  65. use Aqarmap\Bundle\UserBundle\Services\UserActivityService;
  66. use Aqarmap\Bundle\UserBundle\Services\UserManager;
  67. use Aqarmap\Bundle\UserBundle\Services\UserPackagesService;
  68. use Aqarmap\Bundle\UserBundle\Services\UserServicesManager;
  69. use Doctrine\ORM\EntityManager;
  70. use Doctrine\ORM\EntityManagerInterface;
  71. use FOS\RestBundle\Controller\Annotations as Rest;
  72. use FOS\RestBundle\View\View;
  73. use FOS\UserBundle\Model\UserManagerInterface;
  74. use Gedmo\Translatable\TranslatableListener;
  75. use Knp\Component\Pager\PaginatorInterface;
  76. use Psr\Log\LoggerInterface;
  77. use Psr\Log\LogLevel;
  78. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  79. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  80. use Symfony\Component\ExpressionLanguage\Expression;
  81. use Symfony\Component\HttpFoundation\RedirectResponse;
  82. use Symfony\Component\HttpFoundation\Request;
  83. use Symfony\Component\HttpFoundation\Response;
  84. use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
  85. use Symfony\Component\Routing\Attribute\Route;
  86. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  87. use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
  88. use Symfony\Component\Security\Http\Attribute\IsGranted;
  89. use Symfony\Contracts\Translation\TranslatorInterface;
  90. use Vich\UploaderBundle\Handler\DownloadHandler;
  91. /**
  92. * Listing controller.
  93. */
  94. class ListingController extends AbstractController
  95. {
  96. public function __construct(
  97. private readonly Composer $messageComposer,
  98. OtpServiceInterface $otpService,
  99. private readonly DatabaseNotification $databaseNotification,
  100. private readonly LoggerInterface $logger,
  101. private readonly TranslatorInterface $translator,
  102. private readonly FeatureToggleManager $featureToggleManager,
  103. private readonly Setting $setting,
  104. private readonly ListingManager $listingManager,
  105. private readonly EventDispatcherInterface $dispatcher,
  106. private readonly InteractionService $interactionService,
  107. private readonly NeighborhoodManager $neighborhoodManager,
  108. private readonly DefaultListingSearch $defaultListingSearch,
  109. private readonly RelatedResultServiceInterface $relatedResultService,
  110. LocationManagerInterface $locationManager,
  111. private readonly TopSellerRetrievalService $topSellerRetrievalService,
  112. private readonly FinancialAidService $financialAidService,
  113. private readonly ListingLeadManagerInterface $listingLeadManager,
  114. private readonly CompoundFaqsService $compoundFaqsService,
  115. private readonly PaginatorInterface $paginator,
  116. private readonly UserActivityService $userActivityService,
  117. private readonly MobileDetectionService $mobileDetectionService,
  118. private readonly CallRequestManager $callRequestManager,
  119. private readonly AuthorizationCheckerInterface $authorizationChecker,
  120. TokenStorageInterface $tokenStorage,
  121. private readonly MortgageService $mortgageService,
  122. private readonly ListingRuleMatcher $listingRuleMatcher,
  123. private readonly CreditManager $creditManager,
  124. private readonly UserServicesManager $userServicesManager,
  125. private readonly UserManagerInterface $FOSUserManager,
  126. private readonly PhoneManagerInterface $phoneManager,
  127. FreeListingService $freeListingService,
  128. private readonly ListingFeatureService $listingFeatureService,
  129. private readonly UserManager $userManager,
  130. private readonly SpecialAddListingService $specialAddListingService,
  131. private readonly CompoundDetailService $compoundDetailService,
  132. private readonly TranslatableListener $translatableListener,
  133. private readonly UserPackagesRepository $userPackagesRepository,
  134. private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry,
  135. ) {
  136. }
  137. /**
  138. * Listing Details Action.
  139. */
  140. #[Route(path: '/{id}/{notification}', name: 'listing_view', requirements: ['id' => '\d+'], options: ['i18n' => false, 'expose' => true], defaults: ['notification' => 0], methods: ['GET'])]
  141. #[Route(path: '/listing/{id}/notification/{notification}', name: 'listing_view_notification', defaults: ['notification' => 0], methods: ['GET'])]
  142. #[Route(path: '/listing/{id}', name: 'listing_details', requirements: ['id' => '\d+'], options: ['expose' => true], methods: ['GET'])]
  143. #[Route(path: '/listing/{id}-{slug}', name: 'listing_slug', requirements: ['id' => '\d+', 'slug' => '.+'], methods: ['GET'])]
  144. public function read(Request $request, Listing $listing, EntityManagerInterface $em, UserServicesManager $userServicesManager)
  145. {
  146. if ($request->get('notification')) {
  147. try {
  148. $this->databaseNotification->markOneAsRead($request->get('notification'));
  149. } catch (\Exception $exception) {
  150. $this->logger->log(LogLevel::ERROR, $exception->getMessage());
  151. }
  152. }
  153. // Redirect to the right URL if the URL is short-URL or the listing slug is incorrect
  154. if (('listing_slug' != $request->get('_route')
  155. || $request->attributes->get('slug') != $listing->getSlug())
  156. && !empty($listing->getSlug())
  157. ) {
  158. return $this->redirectToRoute('listing_slug', array_merge($request->query->all(), [
  159. 'id' => $listing->getId(),
  160. 'slug' => $listing->getSlug(),
  161. ]), Response::HTTP_MOVED_PERMANENTLY);
  162. }
  163. $listingStatus = !\in_array($listing->getStatus(), [ListingStatus::LIVE, ListingStatus::PENDING]);
  164. if ($listingStatus && !$request->query->get('noredirect') && $listing->getUser() != $this->getUser()) {
  165. if (!$listing->getSection()->getSearchable()) {
  166. return $this->redirectToRoute('compound_search', [], Response::HTTP_FOUND);
  167. }
  168. try {
  169. $searchableLocation = $listing->getLocation()->getNearestSearchable();
  170. } catch (\Exception) {
  171. $this->addFlash(
  172. 'danger',
  173. $this->translator->trans('listing.not_available')
  174. );
  175. return $this->redirectToRoute('homepage', [], Response::HTTP_FOUND);
  176. }
  177. return $this->redirectToRoute('search', [
  178. 'section_slug' => $listing->getSection()->getSlug(),
  179. 'property_type_slug' => $listing->getPropertyType()->getSlug(),
  180. 'location_slug' => $searchableLocation->getSlug(),
  181. ], Response::HTTP_MOVED_PERMANENTLY);
  182. }
  183. $listingRepo = $em->getRepository(Listing::class);
  184. if ($notifierId = $request->query->get('notifier')) {
  185. if (null !== $notifier = $em->getRepository(\Aqarmap\Bundle\NotifierBundle\Entity\Notifier::class)->find($notifierId)) {
  186. $this->dispatcher->dispatch(new NotifierEvent($notifier), 'aqarmap.notifier.interaction');
  187. } else {
  188. $this->logger->warning('Notifier with id: '.$notifierId.' was not found!');
  189. }
  190. }
  191. $this->interactionService->increaseViews($listing, $this->getUser());
  192. $relatedListingCriteriaMapper = $this->relatedResultService->getRelatedListingCriteriaMapper($listing);
  193. $relatedListings = $this->defaultListingSearch->search($relatedListingCriteriaMapper)['searchResults'];
  194. $topSeller = $this->createTopSeller($listing);
  195. $topSearchableCompanies = $this->topSellerRetrievalService->getTopSellerPersonalData($topSeller, $request->getLocale());
  196. $searchableLocation = $listing->getLocation()->getNearestSearchable();
  197. $isPlaceHoldered = false;
  198. $userProfilePhoto = $listing->getUser()->isValidPersonalPhoto() ? $listing->getUser()->getPersonalPhoto() : null;
  199. if ($listing->getLogo() || ($listing->getParent() && $listing->getParent()->getLogo())) {
  200. if ($listing->getParent() && $listing->getParent()->getLogo()) {
  201. }
  202. } elseif (!empty($listing->getPhotosForSlider()) && $listing->getUser()->getIsValidLogo()) {
  203. $isPlaceHoldered = true;
  204. }
  205. $this->financialAidService->setFinancialAidInListing($listing);
  206. $activeListingsCount = $listing->getUser()->getActiveListingsCount();
  207. $leadsCount = 0;
  208. if ($this->featureToggleManager->isEnabled('web.client.served.count')) {
  209. $leadsCount = $listing->getUser()->getClientServedCount();
  210. }
  211. $listing = current(
  212. $this->listingManager->setUserActivities(
  213. [$listing],
  214. [$listing->getId()]
  215. )
  216. );
  217. $hasUserMadeLead = false;
  218. if ($user = $this->getUser()) {
  219. $hasUserMadeLead = $this->listingLeadManager->hasUserMadeLead($listing, $user);
  220. }
  221. $compoundFaqs = [];
  222. $listingDetails = $listing;
  223. if ($listing->isProject()) {
  224. $compoundFaqs = $this->compoundFaqsService->generateFaqData($listing);
  225. $resaleRegularListingsPaginated = $this->paginator->paginate(
  226. $listingRepo->getSimilarListingsInSection($listing, ListingSections::FOR_SALE),
  227. max($request->query->getInt('resalePage', ResaleListingsConstant::PAGE), ResaleListingsConstant::PAGE),
  228. ResaleListingsConstant::LIMIT,
  229. [
  230. 'pageParameterName' => 'resalePage',
  231. 'sortFieldParameterName' => 'resaleSort',
  232. 'sortDirectionParameterName' => 'resaleDirection',
  233. ]
  234. );
  235. $this->compoundDetailService->getPropertyTypeChildrens($listing, $request->getLocale());
  236. $liveUnitsPaginated = $listing->getPropertyTypeChildren();
  237. $this->compoundDetailService->getPropertyTypeUnitsChildrens($listing, ListingSections::FOR_SALE, $request->getLocale());
  238. $resaleUnitsPaginated = $listing->getPropertyTypeChildren();
  239. $this->compoundDetailService->getPropertyTypeUnitsChildrens($listing, ListingSections::FOR_RENT, $request->getLocale());
  240. $rentUnitsPaginated = $listing->getPropertyTypeChildren();
  241. }
  242. $request->cookies->get('user-agent');
  243. $mobileDetection = $this->mobileDetectionService->mobileDetection($request, $return);
  244. $return = [
  245. 'listing' => $listingDetails,
  246. 'searchableLocation' => $searchableLocation,
  247. 'contact_seller_form' => $this->contactSellerForm($listing)->createView(),
  248. 'quick_registration_form' => $this->quickRegistrationForm()->createView(),
  249. 'call_request' => $this->callRequestForm($listing)->createView(),
  250. 'related_listings' => $relatedListings,
  251. 'relatedListingsCount' => (null != $relatedListings) ? $relatedListings->getTotalItemCount() : 0,
  252. 'location_statistics' => $this->neighborhoodManager->getStatistics(
  253. $listing->getLocation()->getNearestNeighborhood(),
  254. $listing->getPropertyType()
  255. ),
  256. 'topSearchableCompanies' => $topSearchableCompanies,
  257. 'topSearchableCompaniesCount' => \count($topSearchableCompanies),
  258. 'isPlaceHoldered' => $isPlaceHoldered,
  259. 'userProfilePhoto' => $userProfilePhoto,
  260. 'activeListingsCount' => $activeListingsCount,
  261. 'leadsCount' => $leadsCount,
  262. 'featureToggle' => $this->userActivityService->getFeatureToggles(),
  263. 'leadAnalytics' => $this->listingManager->getLeadAnalytics($listing),
  264. 'otherUnits' => $this->listingManager->getOtherUnits($listing, $request->getLocale()),
  265. 'hasUserMadeLead' => $hasUserMadeLead,
  266. 'resaleRegularListings' => $resaleRegularListingsPaginated ?? null,
  267. 'resaleUnitsPaginated' => $resaleUnitsPaginated ?? null,
  268. 'liveUnitsPaginated' => $liveUnitsPaginated ?? null,
  269. 'rentUnitsPaginated' => $rentUnitsPaginated ?? null,
  270. 'compoundFaqs' => $compoundFaqs,
  271. 'isMobile' => $mobileDetection['isMobile'],
  272. 'hasCompoundRatingService' => $user ? $userServicesManager->hasActiveService(
  273. UserServicesType::COMPOUND_RATING,
  274. $user->getId()
  275. ) : false,
  276. ];
  277. if ($mobileDetection['isMobile']) {
  278. $nearestLocations = $em
  279. ->getRepository(\Aqarmap\Bundle\ListingBundle\Entity\Location::class)
  280. ->getNearestLocations([$listing->getLocation()]);
  281. $return['nearestLocations'] = $nearestLocations;
  282. foreach ($mobileDetection as $key => $val) {
  283. $return[$key] = $val;
  284. }
  285. return $this->render('@AqarmapListing/Listing/read-mob.html.twig', $return);
  286. }
  287. $return['discussions'] = $em
  288. ->getRepository(\Aqarmap\Bundle\DiscussionBundle\Entity\Discussion::class)
  289. ->getTrendingWithLocation($listing->getLocation()->getId(), 3);
  290. return $this->render('@AqarmapListing/Listing/read.html.twig', $return);
  291. }
  292. /**
  293. * TopSeller Attributes from listing data.
  294. *
  295. * @param Listing
  296. *
  297. * @return TopSeller
  298. */
  299. private function createTopSeller($listing)
  300. {
  301. $topSeller = new TopSeller();
  302. $topSeller->setLocation($listing->getLocation()->getId());
  303. $topSeller->setSection($listing->getSection()->getId());
  304. $topSeller->setPropertyType($listing->getPropertyType()->getId());
  305. return $topSeller;
  306. }
  307. /**
  308. * Latest Listings Action.
  309. */
  310. #[Route(path: '/listing/latest', name: 'listing_latest', methods: ['GET'])]
  311. public function latest(Request $request): Response
  312. {
  313. /** @var EntityManager $em */
  314. $em = $this->managerRegistry->getManager();
  315. $pagination = $this->paginator->paginate(
  316. $em->getRepository(Listing::class)->getLatestListings(),
  317. $request->query->getInt('page', 1),
  318. 25
  319. );
  320. return $this->render('@AqarmapListing/Listing/latest.html.twig', [
  321. 'listings' => $pagination,
  322. ]);
  323. }
  324. /**
  325. * Add Listing First Step Action.
  326. */
  327. #[Route(path: '/listing/initialize', name: 'listing_initialize', options: ['expose' => true])]
  328. public function initialize(Request $request)
  329. {
  330. $user = $this->getUser();
  331. if (false === $this->authorizationChecker->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
  332. return $this->redirectToRoute('aqarmap_add_listing');
  333. }
  334. $isPhoneVerified = true;
  335. if ($user instanceof User) {
  336. $isPhoneVerified = $user->isPhoneVerified();
  337. }
  338. $hasRentalPackage = $this->userPackagesRepository->hasRentalPackage($user);
  339. $initializeOptions = [
  340. 'action' => $this->generateUrl('listing_initialize'),
  341. 'method' => 'POST',
  342. 'em' => $this->managerRegistry->getManager(),
  343. 'is_admin' => $this->authorizationChecker->isGranted('ROLE_ADMIN'),
  344. 'has_rental_package' => $hasRentalPackage,
  345. 'parentUser' => $user,
  346. ];
  347. $form = $this->createForm(ListingInitializeType::class, $listing = new Listing(), $initializeOptions);
  348. $listingEvent = new ListingEvent($listing);
  349. $this->dispatcher->dispatch($listingEvent, 'aqarmap.listing.pre_submitted');
  350. $form->handleRequest($request);
  351. if ($form->isSubmitted() && $form->isValid() && ($isPhoneVerified || $user->getHasActiveSubscription())) {
  352. $listing = $this->listingManager->createDraft($listing);
  353. // Add user phone number to the listing
  354. if ($listing->getUser()->getPhoneNumber()) {
  355. // link user phone with this listing
  356. // TODO: Suggest only the main number for now, later on we will suggest the main + last 2 numbers.
  357. $this->listingManager->suggestListingPhoneNumbers(
  358. [$listing->getUser()->getPhoneNumber()],
  359. $listing
  360. );
  361. }
  362. return $this->redirectToRoute('listing_edit', [
  363. 'id' => $listing->getId(),
  364. ]);
  365. }
  366. return $this->render('@AqarmapListing/Listing/initialize.html.twig', [
  367. 'form' => $form,
  368. 'isPhoneVerified' => $isPhoneVerified,
  369. ]);
  370. }
  371. /**
  372. * Listing Post Action
  373. * This action manage adding & editing listings.
  374. */
  375. #[Route(path: '/listing/{id}/edit/', name: 'listing_edit', requirements: ['id' => '\d+'], options: ['expose' => true])]
  376. #[IsGranted(attribute: new Expression('(is_granted("ROLE_USER") and is_granted("ROLE_OWNER", subject["listing"])) or is_granted("ROLE_ADMIN")'), subject: ['listing'])]
  377. public function post(Request $request, Listing $listing): Response
  378. {
  379. $user = $this->getUser();
  380. // if user doesn't have phone or active subscription, redirect to my listings with an error flash message
  381. if ($user instanceof User && (!$user->isPhoneVerified() && !$user->getHasActiveSubscription())) {
  382. $this->addFlash('danger', $this->translator->trans('listing.notice.cannot_add_before_verify'));
  383. return $this->redirectToRoute('aqarmap_listing_default_mylistings');
  384. }
  385. $form = $this->createForm(ListingType::class, $listing, [
  386. 'action' => $this->generateUrl('listing_edit', ['id' => $listing->getId()]),
  387. 'method' => 'POST',
  388. 'listingIsLiveFor5Days' => $this->listingManager->checkListingLiveDays($listing),
  389. 'is_admin' => $this->authorizationChecker->isGranted('ROLE_ADMIN'),
  390. 'user_country' => $this->setting->getSetting('general', 'country'),
  391. 'user_type' => $listing->getUser()?->getUserType(),
  392. 'isMortgageOptionsEnabled' => $this->featureToggleManager->isEnabled('web.mortgage.options'),
  393. 'isListingMarketPropertyTypesEnabled' => $this->featureToggleManager->isEnabled('web.listing.market.property.types'),
  394. ]);
  395. $form->handleRequest($request);
  396. if ($request->isMethod('POST')) {
  397. if ($form->isSubmitted() && $form->isValid()) {
  398. $this->updateWhatsAppPhones($listing->getUser()->getId(), $request->request->all('phoneIds'), $request->request->all('hasWhatsApp'));
  399. $listing = $this->handleListingPhones($listing, $request->request->all('listing')['phones'] ?? []);
  400. $this->handleUserPhones($listing->getUser(), $request->request->all('listing')['phones'] ?? []);
  401. $this->listingManager->saveListing($listing);
  402. if ($this->featureToggleManager->isEnabled('web.add.listing.translations')) {
  403. if ('en' == $request->getLocale()) {
  404. $criteria = [
  405. 'reversedLocale' => 'ar',
  406. 'title-ar' => $request->request->get('title-ar'),
  407. 'description-ar' => $request->request->get('description-ar'),
  408. ];
  409. } else {
  410. $criteria = [
  411. 'reversedLocale' => 'en',
  412. 'title-en' => $request->request->get('title-en'),
  413. 'description-en' => $request->request->get('description-en'),
  414. ];
  415. }
  416. $this->listingManager->updateListingTranslationsFields($listing, $criteria);
  417. } else {
  418. $this->listingManager->updateListingTranslations($listing);
  419. }
  420. if ($this->featureToggleManager->isEnabled('web.mortgage.options')) {
  421. $propertyRegistrationStatus = $form->has('propertyRegistrationStatus') ? $form->get('propertyRegistrationStatus')->getData() : null;
  422. if (\in_array($propertyRegistrationStatus, PropertyRegistrationStatusOption::getValidMorgageOptions())) {
  423. $this->mortgageService->addEligibleMortgageTypes($listing);
  424. } else {
  425. $this->listingManager->setELigibleMortgageToNull($listing);
  426. }
  427. $listing = $this->mortgageService->addMortgageApproval($listing, $form);
  428. }
  429. $listingEvent = new ListingEvent($listing);
  430. $this->dispatcher->dispatch($listingEvent, 'aqarmap.listing.submitted');
  431. if (
  432. ListingSource::SCRAPPING == $listing->getSource()
  433. || ListingSource::LITE == $listing->getSource()
  434. ) {
  435. return $this->redirectToRoute('listing_view', ['id' => $listing->getId()]);
  436. }
  437. return $this->redirectToRoute('listing_upload', ['id' => $listing->getId(), '_locale' => $request->get('_locale')]);
  438. }
  439. $this->addFlash('danger', $this->translator->trans('static.problem_error_message'));
  440. }
  441. $translatedLocale = 'en' == $request->getLocale() ? 'ar' : 'en';
  442. return $this->render('@AqarmapListing/Listing/post.html.twig', [
  443. 'form' => $form,
  444. 'listing' => $listing,
  445. 'title_translation' => $this->listingTitleTranslations($listing, $translatedLocale),
  446. 'description_translation' => $this->listingDescriptionTranslations($listing, $translatedLocale),
  447. 'brokerChoices' => UserTypes::getBrokerChoices(),
  448. ]);
  449. }
  450. /**
  451. * @return array|mixed
  452. */
  453. private function listingTitleTranslations(Listing $listing, string $locale)
  454. {
  455. $this->translatableListener->setTranslatableLocale($locale);
  456. $listing->setTranslatableLocale($locale);
  457. return $listing->getTitle() ?? $this->listingManager->refreshTranslationsAndGenerateListingTitle($listing, $locale);
  458. }
  459. private function listingDescriptionTranslations(Listing $listing, string $locale)
  460. {
  461. $this->translatableListener->setTranslatableLocale($locale);
  462. $listing->setTranslatableLocale($locale);
  463. return $listing->getDescription() ?? $this->listingManager->refreshTranslationsAndGenerateListingTitle($listing, $locale);
  464. }
  465. private function handleListingPhones(Listing $listing, array $phones): Listing
  466. {
  467. $listing->clearPhones();
  468. foreach ($phones as $phone) {
  469. $phoneNumber = $phone['number'];
  470. $countryCode = $phone['countryCode'];
  471. $phone = new Phone($countryCode.$phoneNumber, $countryCode);
  472. $listing->addPhone(new ListingPhone($phoneNumber, $listing, $countryCode, $phone));
  473. }
  474. return $listing;
  475. }
  476. private function handleUserPhones(User $user, array $phones): void
  477. {
  478. foreach ($phones as $phone) {
  479. $originalPhoneNumber = $phone['number'];
  480. $countryCode = $phone['countryCode'];
  481. $phoneNumber = $this->phoneManager->trimZero($originalPhoneNumber, $countryCode);
  482. $this->phoneManager->addNewUserPhone($phoneNumber, $countryCode, $user, true, false, null, $originalPhoneNumber);
  483. }
  484. }
  485. /**
  486. * Add Listing First Step Action.
  487. */
  488. #[Route(path: '/listing/{id}/edit/photos', name: 'listing_upload', requirements: ['id' => '\d+'], options: ['expose' => true])]
  489. #[IsGranted(attribute: new Expression('(is_granted("ROLE_USER") and is_granted("ROLE_OWNER", subject["listing"])) or is_granted("ROLE_ADMIN")'), subject: ['listing'])]
  490. public function upload(Request $request, Listing $listing)
  491. {
  492. $form = $this->createForm(PhotoType::class, null, [
  493. 'method' => 'POST',
  494. ]);
  495. $form->handleRequest($request);
  496. $listingRules = $this->listingRuleMatcher->match($listing);
  497. if ($form->isSubmitted() && $form->isValid() && $request->isMethod('POST')) {
  498. $listingPhotos = [];
  499. try {
  500. $listingPhotos = $this->listingManager->addListingPhotos($listing, $form->get('file')->getData());
  501. $listingEvent = new ListingEvent($listing);
  502. $this->dispatcher->dispatch($listingEvent, 'aqarmap.listing.submitted');
  503. $this->listingManager->saveListing($listing);
  504. // If not Ajax request
  505. if (!$request->isXmlHttpRequest()) {
  506. $this->logger->error('Not AJAX');
  507. return $this->redirect($request->headers->get('referer') ?: $this->generateUrl('homepage'));
  508. }
  509. } catch (\Exception $exception) {
  510. $this->logger->error($exception->getMessage());
  511. }
  512. return View::create(['files' => $listingPhotos], Response::HTTP_OK);
  513. }
  514. return $this->render('@AqarmapListing/Listing/upload.html.twig', [
  515. 'requiredPhotosCount' => $listingRules['required_photos'] ?? 0,
  516. 'listing' => $listing,
  517. 'form' => $form,
  518. ]);
  519. }
  520. /**
  521. * Should be called after photos are uploaded.
  522. */
  523. #[Route(path: '/listing/{id}/finish', name: 'listing_finish', requirements: ['id' => '\d+'])]
  524. #[IsGranted(attribute: new Expression('(is_granted("ROLE_USER") and is_granted("ROLE_OWNER", subject["listing"])) or is_granted("ROLE_ADMIN")'), subject: ['listing'])]
  525. public function finish(Listing $listing, UserPackagesService $userPackagesService): Response
  526. {
  527. $listingEvent = new ListingEvent($listing);
  528. if (!$userPackagesService->canListInLocation($listing)) {
  529. $this->addFlash('danger', $this->translator->trans('credit.can_not_list_in_location'));
  530. return $this->redirectToRoute('listing_upload', [
  531. 'id' => $listing->getId(),
  532. ], Response::HTTP_FOUND);
  533. }
  534. $this->dispatcher->dispatch($listingEvent, 'aqarmap.listing.submitted');
  535. $response = $listingEvent->getResponse();
  536. if (null === $response) {
  537. if (ListingStatus::PENDING === $listing->getStatus()) {
  538. $this->addFlash(
  539. 'success',
  540. $this->translator->trans('add_listing_page.success_messages.padding_review')
  541. );
  542. if ($this->featureToggleManager->isEnabled('web.delayed.review.eid.info.message')) {
  543. $this->addFlash(
  544. 'info',
  545. $this->translator->trans('add_listing_page.info_messages.eid_delayed_review')
  546. );
  547. }
  548. }
  549. $response = new RedirectResponse(
  550. $this->generateUrl(
  551. 'listing_slug',
  552. ['id' => $listing->getId(), 'slug' => $listing->getSlug()]
  553. )
  554. );
  555. }
  556. return $response;
  557. }
  558. /**
  559. * SWAT special add listing feature page.
  560. */
  561. #[Route(path: '/listing/{id}/feature', name: 'special_add_listing_feature', requirements: ['id' => '\d+'])]
  562. #[IsGranted(attribute: 'ROLE_OWNER', subject: 'listing')]
  563. public function specialAddListingFeature(Listing $listing): RedirectResponse
  564. {
  565. return $this->redirectToRoute('listing_finish', ['id' => $listing->getId()]);
  566. }
  567. /**
  568. * @return RedirectResponse|Response
  569. */
  570. #[Route(path: '/listing/{id}/make_it_featured', name: 'listing_confirm_featured_credit', requirements: ['id' => '\d+'], methods: ['GET'])]
  571. #[IsGranted(attribute: 'ROLE_OWNER', subject: 'listing')]
  572. public function confirmFeaturedPublishing(Listing $listing)
  573. {
  574. if (!\in_array($listing->getStatus(), [ListingStatus::PENDING, ListingStatus::LIVE])) {
  575. $this->addFlash(
  576. 'danger',
  577. $this->translator->trans('listing.featured_failure_statement.not_live_or_pending')
  578. );
  579. return $this->redirectToRoute('aqarmap_listing_default_mylistings');
  580. }
  581. if (UserTypes::INDIVIDUAL == $listing->getUser()->getUserType()) {
  582. $listingRules = $this->listingFeatureService->getFeaturedListingRules($listing, ['sold_by_owner', 'sold_by_owner_sponsored']);
  583. } else {
  584. $listingRules = $this->listingFeatureService->getFeaturedListingRules($listing, ['featured', 'premium', 'sponsored', 'spotlight']);
  585. }
  586. /** @var User $user */
  587. $user = $this->getUser();
  588. return $this->render(
  589. '@AqarmapListing/Listing/featuredListingCheckout.html.twig',
  590. [
  591. 'listingRules' => $listingRules,
  592. 'listingId' => $listing->getId(),
  593. 'haveFeesToFeature' => $this->listingManager->userHasFeesToFeature($user, $listing),
  594. ]
  595. );
  596. }
  597. #[Route(path: '/listing/{id}/make_it_featured', name: 'listing_confirm_publish_featured_credit_post', requirements: ['id' => '\d+'], options: ['expose' => true], methods: ['POST'])]
  598. #[IsGranted(attribute: 'ROLE_OWNER', subject: 'listing')]
  599. public function prepareFeaturedListingPayment(Listing $listing, Request $request): RedirectResponse
  600. {
  601. $listingRules = $this->listingManager->getListingRules($listing);
  602. $fees = $request->query->get('fees', $listingRules['featured_fees']);
  603. $duration = $request->query->get('duration', $listingRules['featured_duration']);
  604. $listingFeaturedType = $request->query->get('listingFeaturedType', ListingFeaturedTypes::FEATURED);
  605. $listingFeature = $request->query->get('listingFeature', ListingFeatures::FEATURED);
  606. $listingStatus = $listing->getStatus();
  607. /** @var User $user */
  608. $user = $this->getUser();
  609. if (!$this->listingManager->isAffordable($user, $fees)) {
  610. $this->addFlash('danger', $this->translator->trans('credit.not_enough_featuring_credit', [
  611. 'link' => $this->generateUrl('aqarmap_buy_credit', ['listing_id' => $listing->getId()]),
  612. ]));
  613. return $this->redirectToRoute('listing_confirm_featured_credit', [
  614. 'id' => $listing->getId(),
  615. ], Response::HTTP_FOUND);
  616. }
  617. $featuredText = $this->translator->trans(ListingFeaturedTypes::getFeaturedText($listingFeaturedType));
  618. $rules = [
  619. 'featuredFees' => $fees,
  620. 'featuredDuration' => $duration,
  621. 'listingFeaturedType' => $listingFeaturedType,
  622. 'listingFeature' => $listingFeature,
  623. 'featuredText' => $featuredText,
  624. ];
  625. if ($this->listingManager->requiresFeaturingReview($listingFeaturedType)) {
  626. try {
  627. $this->listingManager->makeItFeatured($listing, $rules);
  628. if (ListingStatus::LIVE == $listingStatus) {
  629. $this->addFlash(
  630. 'success',
  631. $this->translator->trans('add_listing_page.success_messages.pending_featuring', [':featured_type:' => $featuredText])
  632. );
  633. } else {
  634. $this->addFlash(
  635. 'success',
  636. $this->translator->trans('add_listing_page.success_messages.padding_review')
  637. );
  638. if ($this->featureToggleManager->isEnabled('web.delayed.review.eid.info.message')) {
  639. $this->addFlash(
  640. 'info',
  641. $this->translator->trans('add_listing_page.info_messages.eid_delayed_review')
  642. );
  643. }
  644. }
  645. } catch (\Exception $exception) {
  646. $this->addFlash('danger', $this->translator->trans($exception->getMessage()));
  647. }
  648. if (ListingStatus::LIVE == $listingStatus) {
  649. return $this->redirectToRoute('aqarmap_listing_default_mylistings');
  650. }
  651. return $this->redirectToRoute('listing_finish', [
  652. 'id' => $listing->getId(),
  653. ]);
  654. }
  655. try {
  656. $this->listingManager->makeItFeatured($listing, $rules);
  657. if (ListingStatus::LIVE == $listing->getStatus()) {
  658. $this->addFlash(
  659. 'success',
  660. $this->translator->trans(
  661. 'add_listing_page.success_messages.featured',
  662. [':listing_title:' => $listing->getTitle(), ':featured_type:' => $featuredText]
  663. )
  664. );
  665. return $user->hasValidAccessToLiveApp() ?
  666. $this->redirect(sprintf('%s/%s', $this->getParameter('user_dashboard_url'), 'listings')) :
  667. $this->redirectToRoute('aqarmap_listing_default_mylistings');
  668. }
  669. $this->addFlash(
  670. 'success',
  671. $this->translator->trans('add_listing_page.success_messages.padding_review')
  672. );
  673. if ($this->featureToggleManager->isEnabled('web.delayed.review.eid.info.message')) {
  674. $this->addFlash(
  675. 'info',
  676. $this->translator->trans('add_listing_page.info_messages.eid_delayed_review')
  677. );
  678. }
  679. return $this->redirectToRoute('listing_finish', [
  680. 'id' => $listing->getId(),
  681. ]);
  682. } catch (\Exception $e) {
  683. $buyCreditLink = $this->generateUrl('page_view', ['slug' => 'buy-credit'], true);
  684. if ($this->setting->getSetting('features', 'payments')) {
  685. $buyCreditLink = $this->generateUrl('aqarmap_buy_credit');
  686. }
  687. $this->addFlash('danger', $this->translator->trans(
  688. $e->getMessage(),
  689. ['%link%' => $buyCreditLink],
  690. 'exceptions'
  691. ));
  692. }
  693. return $this->redirectToRoute('listing_confirm_featured_credit', [
  694. 'id' => $listing->getId(),
  695. ], Response::HTTP_FOUND);
  696. }
  697. /**
  698. * @return array|RedirectResponse
  699. */
  700. #[Route(path: '/listing/{id}/payment_confirmation', name: 'listing_confirm_publish_credit', requirements: ['id' => '\d+'], methods: ['GET'])]
  701. #[IsGranted(attribute: new Expression('is_granted("ROLE_USER") and is_granted("ROLE_OWNER", subject["listing"])'), subject: ['listing'])]
  702. public function confirmPublishing(Listing $listing, UserPackagesService $userPackagesService): Response
  703. {
  704. $listingRule = $this->listingRuleMatcher->match($listing);
  705. /** @var EntityManager $em */
  706. $em = $this->managerRegistry->getManager();
  707. // User Balance
  708. $userDetails = $listing->getUser();
  709. $available_balance = $this->userPackagesRepository->getTotalCreditsByUser($userDetails);
  710. $userId = $userDetails->getId();
  711. $em->getRepository(User::class)->findOneBy(['id' => $userId]);
  712. $listingManger = $this->listingManager;
  713. if (!$userPackagesService->canListInLocation($listing)) {
  714. $this->addFlash('danger', $this->translator->trans('credit.can_not_list_in_location'));
  715. return $this->redirectToRoute('listing_upload', [
  716. 'id' => $listing->getId(),
  717. ], Response::HTTP_FOUND);
  718. }
  719. $userServiceRepository = $em->getRepository(UserServices::class);
  720. if (
  721. $this->userServicesManager->hasActiveService(UserServicesType::UNLIMITED_LISTINGS, $userId)
  722. && ListingCategories::UNLIMITED != $listing->getCategory()
  723. && ListingStatus::DRAFT != $listing->getStatus()
  724. ) {
  725. if (
  726. $userServiceRepository
  727. ->passUnlimitedListing($listing, $this->creditManager, $listingManger) || $listing->getPublicationCredit()
  728. ) {
  729. if ($this->listingFeatureService->isFeaturedOffersAvailable($listing)) {
  730. return $this->redirectToRoute('listing_confirm_featured_credit', [
  731. 'id' => $listing->getId(),
  732. ]);
  733. }
  734. return $this->redirectToRoute('listing_finish', [
  735. 'id' => $listing->getId(),
  736. ]);
  737. }
  738. }
  739. $isUserInSpecialAddListingGroup = $this->specialAddListingService->hasSpecialAddListingGroup($userDetails);
  740. $userUnlimitedListings = $this->userServicesManager->hasActiveService(UserServicesType::UNLIMITED_LISTINGS, $userId);
  741. $userHasNoFreeListingService = !$userUnlimitedListings;
  742. $userHasNoFreeListingQuote = ($userUnlimitedListings && 0 == $userUnlimitedListings['remainingQuota']);
  743. if ($isUserInSpecialAddListingGroup) {
  744. if ($userHasNoFreeListingService || $userHasNoFreeListingQuote) {
  745. return $this->redirectToRoute('special_add_listing_feature', ['id' => $listing->getId()]);
  746. }
  747. }
  748. $form = $this->ConfirmPaymentForm($listing);
  749. return $this->render(
  750. '@AqarmapListing/Listing/confirmPublishing.html.twig',
  751. [
  752. 'listing' => $listing,
  753. 'fees' => $listingRule['publication_fees'],
  754. 'duration' => $listingRule['duration'],
  755. 'available_balance' => $available_balance,
  756. 'form' => $form->createView(),
  757. ]
  758. );
  759. }
  760. /**
  761. * @return array
  762. */
  763. #[Route(path: '/listing/{id}/list_rejections', name: 'listing_list_rejections', requirements: ['id' => '\d+'], methods: ['GET'])]
  764. #[IsGranted(attribute: 'ROLE_OWNER', subject: 'listing')]
  765. public function listRejections(Listing $listing, Request $request): Response
  766. {
  767. if ($request->get('notification')) {
  768. try {
  769. $this->databaseNotification->markOneAsRead($request->get('notification'));
  770. } catch (\Exception $exception) {
  771. $this->logger->log(LogLevel::ERROR, $exception->getMessage());
  772. }
  773. }
  774. return $this->render(
  775. '@AqarmapListing/Listing/confirmPublishing.html.twig',
  776. [
  777. 'listing' => $listing,
  778. ]
  779. );
  780. }
  781. /**
  782. * @return array|RedirectResponse
  783. */
  784. #[Route(path: '/listing/{id}/payment_confirmation', name: 'listing_confirm_publish_credit_post', requirements: ['id' => '\d+'], methods: ['POST'])]
  785. #[IsGranted(attribute: new Expression('is_granted("ROLE_USER") and is_granted("ROLE_OWNER", subject["listing"])'), subject: ['listing'])]
  786. public function prepareListingPayment(Listing $listing, Request $request, UserPackagesService $userPackagesService): RedirectResponse
  787. {
  788. $listingRule = $this->listingRuleMatcher->match($listing);
  789. /** @var EntityManager $em */
  790. $em = $this->managerRegistry->getManager();
  791. // User Balance
  792. $available_balance = $this->userPackagesRepository->getTotalCreditsByUser($listing->getUser());
  793. $userId = $listing->getUser()->getId();
  794. $listingManger = $this->listingManager;
  795. if (!$userPackagesService->canListInLocation($listing)) {
  796. $this->addFlash('danger', $this->translator->trans('credit.can_not_list_in_location'));
  797. return $this->redirectToRoute('listing_upload', [
  798. 'id' => $listing->getId(),
  799. ], Response::HTTP_FOUND);
  800. }
  801. $userServiceRepository = $em->getRepository(UserServices::class);
  802. if ($this->userServicesManager->hasActiveService(UserServicesType::UNLIMITED_LISTINGS, $userId)) {
  803. if ($userServiceRepository->passUnlimitedListing($listing, $this->creditManager, $listingManger)) {
  804. if (!$this->listingFeatureService->isFeaturedOffersAvailable($listing)) {
  805. return $this->redirectToRoute('listing_finish', [
  806. 'id' => $listing->getId(),
  807. ]);
  808. }
  809. return $this->redirectToRoute('listing_confirm_featured_credit', [
  810. 'id' => $listing->getId(),
  811. ]);
  812. }
  813. }
  814. $form = $this->ConfirmPaymentForm($listing);
  815. $form->handleRequest($request);
  816. if ($form->isSubmitted() && $form->isValid()) {
  817. // Payment Confirmed?
  818. // If user cancel payment redirect him to his listing,
  819. if (!$form->get('confirm')->isClicked()) {
  820. return $this->redirectToRoute('listing_view', [
  821. 'id' => $listing->getId(),
  822. ], Response::HTTP_FOUND);
  823. }
  824. // If user credit expired, complain.
  825. $enforceCredit = $this->setting->getSetting('features', 'enforce_credits_expiration');
  826. /** @var User $user */
  827. $user = $this->getUser();
  828. if ($enforceCredit && 0 === $user->getAbsoluteCreditExpiryDays()) {
  829. $this->addFlash('danger', $this->translator->trans('credit.credit_can_not_use', [
  830. 'link' => $this->generateUrl('aqarmap_buy_credit', ['listing_id' => $listing->getId()]),
  831. ]));
  832. return $this->redirectToRoute('listing_confirm_publish_credit', [
  833. 'id' => $listing->getId(),
  834. ], Response::HTTP_FOUND);
  835. }
  836. // User already paid
  837. if ($listing->getPublicationCredit()) {
  838. $this->addFlash('info', $this->translator->trans('credit.already_paid'));
  839. } else {
  840. // User don't have enough credits
  841. if ($listingRule['publication_fees'] > $available_balance) {
  842. $this->addFlash('danger', $this->translator->trans('credit.not_enough_credits'));
  843. } else {
  844. // Subtract publication fees
  845. $credits = $this->creditManager->deduction(
  846. $listing->getUser(),
  847. $listingRule['publication_fees'],
  848. 'Listing Fees'
  849. );
  850. foreach ($credits as $credit) {
  851. if ($credit instanceof Credit) {
  852. $credit->setStatus(CreditStatus::SUCCESS);
  853. $this->listingManager->addFeature($listing, ListingFeatures::PAID, null, $credit);
  854. if (!$this->listingFeatureService->isFeaturedOffersAvailable($listing)) {
  855. return $this->redirectToRoute('listing_finish', [
  856. 'id' => $listing->getId(),
  857. ]);
  858. }
  859. }
  860. }
  861. return $this->redirectToRoute('listing_confirm_featured_credit', [
  862. 'id' => $listing->getId(),
  863. 'ref' => 'add_listing',
  864. ]);
  865. }
  866. }
  867. }
  868. $this->addFlash('danger', 'Unexpected error occurred.');
  869. return $this->redirectToRoute('listing_confirm_publish_credit', [
  870. 'id' => $listing->getId(),
  871. ]);
  872. }
  873. private function ConfirmPaymentForm(Listing $listing)
  874. {
  875. return $this->createForm(ConfirmFormType::class, null, [
  876. 'method' => 'POST',
  877. 'action' => $this->generateUrl('listing_confirm_publish_credit_post', ['id' => $listing->getId()]),
  878. ]);
  879. }
  880. /**
  881. * @return \Symfony\Component\Form\Form
  882. */
  883. private function confirmFeaturedPaymentForm(Listing $listing)
  884. {
  885. return $this->createForm(ConfirmFeaturedFormType::class, null, [
  886. 'method' => 'POST',
  887. 'action' => $this
  888. ->generateUrl(
  889. 'listing_confirm_publish_featured_credit_post',
  890. ['id' => $listing->getId()]
  891. ),
  892. ]);
  893. }
  894. /**
  895. * Delete Listing entity.
  896. */
  897. #[Route(path: '/listing/{id}/delete', name: 'listing_delete', requirements: ['id' => '\d+'], options: ['expose' => true])]
  898. #[IsGranted(attribute: new Expression('is_granted("ROLE_USER") and is_granted("ROLE_OWNER", subject["listing"])'), subject: ['listing'])]
  899. public function delete(Listing $listing, Request $request): RedirectResponse
  900. {
  901. if (\in_array('ROLE_PREVENT_DELETE_LISTING', $this->getUser()->getRoles())) {
  902. throw new AccessDeniedHttpException("Forbidden, user don't have this permission.");
  903. }
  904. if (ListingStatus::EXPIRED == $listing->getStatus()) {
  905. throw $this->createNotFoundException('Listing is expired.');
  906. }
  907. $this->listingManager->remove($listing, ListingStatus::USER_DELETED);
  908. $this->addFlash(
  909. 'success',
  910. $this->translator->trans('add_listing_page.success_messages.deleted')
  911. );
  912. return $this->redirect($request->headers->get('referer') ?: $this->generateUrl('homepage'));
  913. }
  914. /**
  915. * unDelete Listing entity.
  916. */
  917. #[Route(path: '/listing/{id}/undelete', name: 'listing_undelete', requirements: ['id' => '\d+'])]
  918. #[IsGranted(attribute: new Expression('is_granted("ROLE_USER") and is_granted("ROLE_OWNER", subject["listing"])'), subject: ['listing'])]
  919. public function undelete(Listing $listing): Response
  920. {
  921. if (ListingStatus::USER_DELETED != $listing->getStatus()) {
  922. throw $this->createNotFoundException('Unable to find this listing.');
  923. }
  924. $this->managerRegistry->getManager();
  925. $this->listingManager->changeStatus($listing, ListingStatus::PENDING);
  926. $listingEvent = new ListingEvent($listing);
  927. $this->dispatcher->dispatch($listingEvent, 'aqarmap.listing.submitted');
  928. $response = $listingEvent->getResponse();
  929. if (null === $response) {
  930. if (ListingStatus::PENDING === $listing->getStatus()) {
  931. $this->addFlash(
  932. 'success',
  933. $this->translator->trans('add_listing_page.success_messages.padding_review')
  934. );
  935. if ($this->featureToggleManager->isEnabled('web.delayed.review.eid.info.message')) {
  936. $this->addFlash(
  937. 'info',
  938. $this->translator->trans('add_listing_page.info_messages.eid_delayed_review')
  939. );
  940. }
  941. }
  942. $response = new RedirectResponse(
  943. $this->generateUrl(
  944. 'listing_slug',
  945. ['id' => $listing->getId(), 'slug' => $listing->getSlug()]
  946. )
  947. );
  948. }
  949. return $response;
  950. }
  951. /**
  952. * Creates "Request a Call" button.
  953. *
  954. * @return \Symfony\Component\Form\Form
  955. */
  956. public function callRequestForm(Listing $listing)
  957. {
  958. return $this->createForm(CallRequestFormType::class, null, [
  959. 'method' => 'POST',
  960. 'action' => $this->generateUrl('aqarmap_listing_call_request', ['id' => $listing->getId()]),
  961. ]);
  962. }
  963. /**
  964. * Creates contact seller form.
  965. *
  966. * @param Listing $listing The listing entity
  967. *
  968. * @return \Symfony\Component\Form\Form The form
  969. */
  970. public function contactSellerForm(Listing $listing)
  971. {
  972. return $this->createForm(ContactSellerFormType::class, null, [
  973. 'method' => 'POST',
  974. 'action' => $this->generateUrl('aqarmap_listing_contact_seller', ['id' => $listing->getId()]),
  975. ]);
  976. }
  977. /**
  978. * Creates contact seller form.
  979. *
  980. * @param Listing $listing The listing entity
  981. *
  982. * @return \Symfony\Component\Form\Form The form
  983. */
  984. public function contactSellerWideForm(Listing $listing)
  985. {
  986. return $this->createForm(ContactSellerWideFormType::class, null, [
  987. 'method' => 'POST',
  988. 'action' => $this->generateUrl('aqarmap_listing_contact_seller', ['id' => $listing->getId()]),
  989. ]);
  990. }
  991. /**
  992. * Creates quick registration form.
  993. *
  994. * @return \Symfony\Component\Form\Form The form
  995. */
  996. public function quickRegistrationForm()
  997. {
  998. return $this->createForm(QuickRegistrationFormType::class, null, [
  999. 'method' => 'POST',
  1000. 'action' => $this->generateUrl('aqarmap_user_quick_registration'),
  1001. ]);
  1002. }
  1003. /**
  1004. * Contact Seller Action.
  1005. *
  1006. * @return array|\Symfony\Component\Form\Form
  1007. */
  1008. #[Route(path: '/listing/{id}/contact_seller', name: 'aqarmap_listing_contact_seller', requirements: ['id' => '\d+'], options: ['expose' => true], defaults: ['_format' => 'json'], methods: ['POST'])]
  1009. #[Rest\View]
  1010. public function contactSeller(Listing $listing, Request $request)
  1011. {
  1012. $form = $this->contactSellerForm($listing);
  1013. if ($request->request->has('contact_seller_wide')) {
  1014. $form = $this->contactSellerWideForm($listing);
  1015. }
  1016. $lead = $request->get('lead');
  1017. $originalPhoneNumber = $lead['phone'];
  1018. $phoneNumber = $lead ? $this->phoneManager->trimZero($lead['phone'], $request->get('countryCode')) : null;
  1019. $message = json_decode((string) $request->get('message'), true);
  1020. $hasEmail = !$request->get('isAutoGeneratedEmail', 0);
  1021. $user = $this->getUser();
  1022. $form->handleRequest($request);
  1023. if ($lead) {
  1024. $form = $this->createForm(QuickLeadType::class, null, [
  1025. 'method' => 'POST',
  1026. 'csrf_protection' => false,
  1027. ]);
  1028. /** @var User $user */
  1029. $user = $this->FOSUserManager->findUserByEmail($lead['email']);
  1030. if (!$user && !$hasEmail) {
  1031. $user = $this->userManager->findLatestByPhone($phoneNumber, $request->get('countryCode'));
  1032. }
  1033. if (!$user && $hasEmail) {
  1034. $user = $this->userManager->findAndReplaceUserAndEmail($phoneNumber, $lead['email'], $request->get('countryCode'));
  1035. }
  1036. $phone = $this->phoneManager->findOrSavePhoneNumber($phoneNumber, $request->get('countryCode'), null, $originalPhoneNumber);
  1037. if (!$user) {
  1038. $user = $this->FOSUserManager->createUser();
  1039. $user
  1040. ->setFullName($lead['name'])
  1041. ->setPhoneNumber($lead['phone'])
  1042. ->setTempOriginalPhoneNumber($originalPhoneNumber)
  1043. ->setEmail($lead['email'])
  1044. ->setHasEmail($hasEmail)
  1045. ->setIsQucikRegistered(true)
  1046. ;
  1047. $this->userManager->quickRegister($user, $form, $request, true, false, false);
  1048. $registerMessage = $this->translator->trans('popup_form.success');
  1049. }
  1050. $this->phoneManager->addNewUserPhone($phoneNumber, $request->get('countryCode'), $user, true, false, null, $originalPhoneNumber);
  1051. }
  1052. if ($request->isMethod('POST')) {
  1053. // Get current logged in user
  1054. $user ??= $this->getUser();
  1055. $composer = $this->messageComposer;
  1056. $composer
  1057. ->setSender($user)
  1058. ->compose($message['content'], $listing, null, $message['type'] ?? LeadTypes::SEND_MESSAGE)
  1059. ;
  1060. $this->addFlash(
  1061. 'success',
  1062. $this->translator->trans('add_listing_page.success_messages.message_sent')
  1063. );
  1064. return [
  1065. 'status' => 'ok',
  1066. 'message' => $this->translator->trans('listing.success_message_seller'),
  1067. ];
  1068. }
  1069. return $form;
  1070. }
  1071. /**
  1072. * Contact Seller Action.
  1073. */
  1074. #[Route(path: '/listing/{id}/call_request', name: 'aqarmap_listing_call_request', requirements: ['id' => '\d+', '_format' => 'json'], options: ['expose' => true], defaults: ['_format' => 'json'], methods: ['POST'])]
  1075. #[Rest\View]
  1076. public function callRequest(Request $request, Listing $listing)
  1077. {
  1078. $lead = $request->get('lead');
  1079. $countryCode = $request->get('countryCode', $lead['countryCode'] ?? '+20');
  1080. $originalPhoneNumber = $lead['phone'];
  1081. $phoneNumber = $lead ? $this->phoneManager->trimZero($lead['phone'], $countryCode) : null;
  1082. $user = $this->getUser();
  1083. $registerMessage = '';
  1084. $callRequest = new CallRequest();
  1085. if ($lead) {
  1086. $form = $this->createForm(QuickLeadType::class, null, [
  1087. 'method' => 'POST',
  1088. 'csrf_protection' => false,
  1089. ]);
  1090. $form->handleRequest($request);
  1091. $hasEmail = !$request->get('isAutoGeneratedEmail', 0);
  1092. /** @var User $user */
  1093. $user = $this->FOSUserManager->findUserByEmail($lead['email']);
  1094. if (!$user && !$hasEmail) {
  1095. $user = $this->userManager->findLatestByPhone($phoneNumber, $countryCode);
  1096. }
  1097. if (!$user && $hasEmail) {
  1098. $user = $this->userManager->findAndReplaceUserAndEmail($phoneNumber, $lead['email'], $request->get('countryCode'));
  1099. }
  1100. $phone = $this->phoneManager->findOrSavePhoneNumber($phoneNumber, $countryCode, null, $originalPhoneNumber);
  1101. if (!$user) {
  1102. $user = $this->FOSUserManager->createUser();
  1103. $user
  1104. ->setFullName($lead['name'])
  1105. ->setPhoneNumber($lead['phone'])
  1106. ->setTempCountryCode($countryCode)
  1107. ->setTempOriginalPhoneNumber($originalPhoneNumber)
  1108. ->setEmail($lead['email'])
  1109. ->setHasEmail($hasEmail)
  1110. ->setIsQucikRegistered(true)
  1111. ;
  1112. $this->userManager->quickRegister($user, $form, $request, true, false, false);
  1113. $registerMessage = $this->translator->trans('popup_form.success');
  1114. }
  1115. $callRequest->setPhone($phone);
  1116. $callRequest->setLeadEmail($lead['email']);
  1117. $callRequest->setLeadFullName($lead['name']);
  1118. $this->phoneManager->addNewUserPhone($phoneNumber, $countryCode, $user, true, false, null, $originalPhoneNumber);
  1119. }
  1120. // Create Call Request
  1121. $callRequest->setUser($user);
  1122. $callRequest->setListing($listing);
  1123. $form = $this->callRequestForm($listing);
  1124. $form->handleRequest($request);
  1125. $isPostRequestAndValidForm = $request->isMethod('POST') && ($form->isSubmitted() && $form->isValid());
  1126. $isNotCheckForm = (false == $request->get('check_form'));
  1127. if ($isPostRequestAndValidForm || $isNotCheckForm) {
  1128. try {
  1129. $this->callRequestManager->submitCallRequest($callRequest, null, $request->query->get('sourceRoute', ''));
  1130. return [
  1131. 'status' => 'ok',
  1132. 'message' => $this->translator->trans('listing.success_call_request'),
  1133. 'registerMessage' => $registerMessage,
  1134. ];
  1135. } catch (InvalidLeadValidationHttpException $exception) {
  1136. return [
  1137. 'status' => $exception->getStatusCode(),
  1138. 'message' => $exception->getMessage(),
  1139. ];
  1140. }
  1141. }
  1142. return $form;
  1143. }
  1144. /**
  1145. * Show how to add a listing for new users.
  1146. */
  1147. #[Route(path: '/add_listing', name: 'aqarmap_add_listing')]
  1148. public function addListingRoles(): Response
  1149. {
  1150. return $this->render(
  1151. '@AqarmapListing/Listing/addListingRoles.html.twig',
  1152. []
  1153. );
  1154. }
  1155. /**
  1156. * @return RedirectResponse|Response
  1157. */
  1158. #[Route(path: '/listing/{id}/relist', name: 'aqarmap_listing_relist', requirements: ['id' => '\d+'], options: ['expose' => true])]
  1159. #[IsGranted(attribute: new Expression('is_granted("ROLE_USER") and is_granted("ROLE_OWNER", subject["listing"])'), subject: ['listing'])]
  1160. public function relist(Listing $listing)
  1161. {
  1162. if (!\in_array($listing->getStatus(), [ListingStatus::EXPIRED, ListingStatus::USER_DELETED])) {
  1163. throw new LogicHttpException('Whoops! Looks like the listing you are trying to relist in not expired!');
  1164. }
  1165. $em = $this->managerRegistry->getManager();
  1166. $listingRepo = $em->getRepository(Listing::class);
  1167. $relistChild = $listingRepo->getRelistChild($listing);
  1168. if ($relistChild) {
  1169. $this->addFlash(
  1170. 'success',
  1171. $this->translator->trans('add_listing_page.success_messages.relist')
  1172. );
  1173. return new RedirectResponse(
  1174. $this
  1175. ->generateUrl(
  1176. 'listing_slug',
  1177. [
  1178. 'id' => $relistChild->getId(),
  1179. 'slug' => $relistChild->getSlug(),
  1180. ]
  1181. )
  1182. );
  1183. }
  1184. $listing = $this->listingManager->relist($listing);
  1185. // Check if the listing requires more photos or requires repayment, and redirection.
  1186. $listingEvent = new ListingEvent($listing);
  1187. $this->dispatcher->dispatch($listingEvent, 'aqarmap.listing.resubmitted');
  1188. $response = $listingEvent->getResponse();
  1189. if (null === $response) {
  1190. if (\in_array($listing->getStatus(), [ListingStatus::PENDING, ListingStatus::LIVE])) {
  1191. $this->addFlash(
  1192. 'success',
  1193. $this->translator->trans('add_listing_page.success_messages.relist')
  1194. );
  1195. }
  1196. $response = new RedirectResponse(
  1197. $this
  1198. ->generateUrl(
  1199. 'listing_slug',
  1200. [
  1201. 'id' => $listing->getId(),
  1202. 'slug' => $listing->getSlug(),
  1203. ]
  1204. )
  1205. );
  1206. }
  1207. return $response;
  1208. }
  1209. #[Route(path: '/listing/{id}/pump_up', name: 'confirm_listing_pump_up', requirements: ['id' => '\d+'], methods: ['POST'])]
  1210. #[IsGranted(attribute: 'ROLE_OWNER', subject: 'listing')]
  1211. public function confirmPumpUpListing(Listing $listing, Request $request): RedirectResponse
  1212. {
  1213. $pumpUpForm = $this->confirmFeaturedPaymentForm($listing);
  1214. $pumpUpForm->handleRequest($request);
  1215. if ($request->isMethod('POST') && $pumpUpForm->isValid()) {
  1216. if (!$pumpUpForm->get('confirm')->isClicked()) {
  1217. return $this->redirectToRoute('aqarmap_listing_default_mylistings', [], Response::HTTP_FOUND);
  1218. }
  1219. try {
  1220. $this->listingManager->pumpUp($listing);
  1221. $this->addFlash(
  1222. 'success',
  1223. $this->translator->trans('credit.pump_up_success', [':listing_title:' => $listing->getTitle()])
  1224. );
  1225. } catch (\Exception $e) {
  1226. $this->addFlash('danger', $e->getMessage());
  1227. }
  1228. }
  1229. return $this->redirectToRoute('aqarmap_listing_default_mylistings');
  1230. }
  1231. /**
  1232. * @return array|RedirectResponse
  1233. */
  1234. #[Route(path: '/listing/{id}/pump_up', name: 'listing_pump_up', requirements: ['id' => '\d+'], methods: ['GET'])]
  1235. #[IsGranted(attribute: 'ROLE_OWNER', subject: 'listing')]
  1236. public function pumpUpListing(Listing $listing)
  1237. {
  1238. $listingRules = $this->listingManager->getListingRules($listing);
  1239. $listingOwner = $listing->getUser();
  1240. $pumpUpFees = $listingRules['pump_up_fees'];
  1241. $pumpUpOccurrence = $listingRules['pump_up_occurrence'];
  1242. $pumpUpDuration = $listingRules['pump_up_duration'];
  1243. if (!$this->listingManager->isPumpUpAvailable($listingRules)) {
  1244. $this->addFlash(
  1245. 'danger',
  1246. $this->translator->trans('credit.pump_up_not_available')
  1247. );
  1248. return $this->redirectToRoute('aqarmap_listing_default_mylistings');
  1249. }
  1250. if (!$this->listingManager->isAffordable($listingOwner, $pumpUpFees)) {
  1251. $this->addFlash(
  1252. 'danger',
  1253. $this->translator->trans('credit.pump_up_can_not_use')
  1254. );
  1255. return $this->redirectToRoute('aqarmap_listing_default_mylistings');
  1256. }
  1257. /** @var EntityManager $em */
  1258. $em = $this->managerRegistry->getManager();
  1259. $em->getRepository(Credit::class);
  1260. $pumpUpForm = $this->createForm(ConfirmFeaturedFormType::class, null, [
  1261. 'method' => 'POST',
  1262. 'action' => $this
  1263. ->generateUrl(
  1264. 'listing_pump_up',
  1265. ['id' => $listing->getId()]
  1266. ),
  1267. 'confirm_message' => $this->translator->trans('listing.pump_up.confirm'),
  1268. 'cancel_message' => $this->translator->trans('listing.pump_up.cancel'),
  1269. ]);
  1270. return $this->render(
  1271. '@AqarmapListing/Listing/pumpUpListing.html.twig',
  1272. [
  1273. 'fees' => $pumpUpFees,
  1274. 'duration' => $pumpUpDuration,
  1275. 'occurrence' => $pumpUpOccurrence,
  1276. 'form' => $pumpUpForm,
  1277. ]
  1278. );
  1279. }
  1280. /**
  1281. * Delete Listing Photos.
  1282. *
  1283. * @throws \Doctrine\ORM\OptimisticLockException
  1284. */
  1285. #[Route(path: '/photos/delete', name: 'listing_bulk_delete_photo', requirements: ['id' => '\d+'])]
  1286. public function bulkDeletePhoto(Request $request, ListingPhotoRepository $listingPhotoRepository, ListingManager $listingManager): RedirectResponse
  1287. {
  1288. $photosIds = $request->get('photos');
  1289. try {
  1290. if ($photosIds) {
  1291. $photos = $listingPhotoRepository->getPhotos($photosIds);
  1292. $listingManager->bulkDeletePhotos($photos);
  1293. } else {
  1294. $this->addFlash('danger', 'No photos selected for deletion');
  1295. }
  1296. } catch (\Exception) {
  1297. $this->addFlash('danger', 'Something went wrong');
  1298. }
  1299. return $this->redirect($request->headers->get('referer') ?: $this->generateUrl('homepage'));
  1300. }
  1301. /**
  1302. * @throws \Exception
  1303. */
  1304. #[Route(path: '/brochure/{file}/download', name: 'download_brochure')]
  1305. public function downloadBrochure(File $file, DownloadHandler $downloadHandler): Response
  1306. {
  1307. $response = $downloadHandler->downloadObject($file, 'file');
  1308. // force specific content type instead of using default "application/octet-stream".
  1309. $response->headers->set('content-type', 'application/pdf');
  1310. return $response;
  1311. }
  1312. /**
  1313. * Update WhatsApp Phones.
  1314. */
  1315. private function updateWhatsAppPhones(int $userId, array $phones = [], array $whatsApp = []): void
  1316. {
  1317. foreach ($phones as $index => $phone) {
  1318. $whatsApp[$index] = isset($whatsApp[$index]) && 'true' === $whatsApp[$index];
  1319. $this->phoneManager->updateWhatsApp($userId, (int) $phone, $whatsApp[$index], true);
  1320. }
  1321. }
  1322. }