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