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