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