src/Aqarmap/Bundle/ListingBundle/Controller/Api/ListingController.php line 92
<?phpnamespace Aqarmap\Bundle\ListingBundle\Controller\Api;use App\Exception\BadRequestHttpException;use App\Exception\LogicHttpException;use Aqarmap\Bundle\CreditBundle\Constant\CreditStatus;use Aqarmap\Bundle\CreditBundle\Contract\CreditManagerInterface;use Aqarmap\Bundle\CreditBundle\Entity\Credit;use Aqarmap\Bundle\FeatureToggleBundle\Service\FeatureToggleManager;use Aqarmap\Bundle\ListingBundle\Constant\CountryCodes;use Aqarmap\Bundle\ListingBundle\Constant\LeadTypes;use Aqarmap\Bundle\ListingBundle\Constant\ListingFeaturedTypes;use Aqarmap\Bundle\ListingBundle\Constant\ListingFeatures;use Aqarmap\Bundle\ListingBundle\Constant\ListingStatus;use Aqarmap\Bundle\ListingBundle\Constant\ListingSyncedFields;use Aqarmap\Bundle\ListingBundle\Constant\PhotoTypes;use Aqarmap\Bundle\ListingBundle\Contracts\PhoneManagerInterface;use Aqarmap\Bundle\ListingBundle\Entity\CallRequest;use Aqarmap\Bundle\ListingBundle\Entity\Listing;use Aqarmap\Bundle\ListingBundle\Entity\ListingNote;use Aqarmap\Bundle\ListingBundle\Entity\ListingPhone;use Aqarmap\Bundle\ListingBundle\Entity\ListingPhoto;use Aqarmap\Bundle\ListingBundle\Entity\Photo;use Aqarmap\Bundle\ListingBundle\Entity\PropertyType;use Aqarmap\Bundle\ListingBundle\Entity\Section;use Aqarmap\Bundle\ListingBundle\Event\LeadEvent;use Aqarmap\Bundle\ListingBundle\Event\ListingEvent;use Aqarmap\Bundle\ListingBundle\Event\ListingUpdatedEvent;use Aqarmap\Bundle\ListingBundle\Form\ContactSellerFormType;use Aqarmap\Bundle\ListingBundle\Form\ListingApiType;use Aqarmap\Bundle\ListingBundle\Form\ListingNoteType;use Aqarmap\Bundle\ListingBundle\Form\PhotoType;use Aqarmap\Bundle\ListingBundle\Form\QuickContactSellerType;use Aqarmap\Bundle\ListingBundle\Form\QuickCreateLeadFormType;use Aqarmap\Bundle\ListingBundle\Form\QuickLeadType;use Aqarmap\Bundle\ListingBundle\Model\LeadModel;use Aqarmap\Bundle\ListingBundle\Repository\ListingNoteRepository;use Aqarmap\Bundle\ListingBundle\Repository\ListingRepository;use Aqarmap\Bundle\ListingBundle\Service\CallRequestManager;use Aqarmap\Bundle\ListingBundle\Service\FavouriteService;use Aqarmap\Bundle\ListingBundle\Service\InteractionService;use Aqarmap\Bundle\ListingBundle\Service\LeadService;use Aqarmap\Bundle\ListingBundle\Service\ListingManager;use Aqarmap\Bundle\ListingBundle\Service\ListingNoteService;use Aqarmap\Bundle\ListingBundle\Service\ListingRateService;use Aqarmap\Bundle\ListingBundle\Service\ListingRuleMatcher;use Aqarmap\Bundle\ListingBundle\Service\LocationManager;use Aqarmap\Bundle\ListingBundle\Service\Mortgage\MortgageService;use Aqarmap\Bundle\ListingBundle\Service\NewsFeed\ListingNewsFeed;use Aqarmap\Bundle\ListingBundle\Twig\ListingExtension;use Aqarmap\Bundle\MainBundle\Controller\Api\BaseController;use Aqarmap\Bundle\MainBundle\Service\Setting;use Aqarmap\Bundle\MessageBundle\Service\Composer;use Aqarmap\Bundle\TopSellerBundle\Model\TopSeller;use Aqarmap\Bundle\TopSellerBundle\Service\TopSellerRetrievalService;use Aqarmap\Bundle\UserBundle\Entity\User;use Aqarmap\Bundle\UserBundle\Services\UserManager;use Doctrine\Common\Collections\Collection;use Doctrine\ORM\EntityManager;use Doctrine\ORM\EntityManagerInterface;use Doctrine\ORM\OptimisticLockException;use Doctrine\ORM\ORMException;use FOS\RestBundle\Controller\Annotations as Rest;use FOS\RestBundle\View\View;use FOS\UserBundle\Model\UserManagerInterface;use Gedmo\Translatable\TranslatableListener;use Knp\Component\Pager\PaginatorInterface;use OpenApi\Attributes as OA;use Symfony\Bridge\Doctrine\Attribute\MapEntity;use Symfony\Component\EventDispatcher\EventDispatcherInterface;use Symfony\Component\ExpressionLanguage\Expression;use Symfony\Component\Form\Form;use Symfony\Component\HttpFoundation\File\UploadedFile;use Symfony\Component\HttpFoundation\Request;use Symfony\Component\HttpFoundation\Response;use Symfony\Component\HttpKernel\Attribute\Cache;use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;use Symfony\Component\Security\Http\Attribute\IsGranted;use Symfony\Contracts\Translation\TranslatorInterface;/*** Class ListingController.*/class ListingController extends BaseController{/*** @var TokenStorageInterface*/protected $tokenStorage;/** @var FOSUserManager */private $fosUserManager;public function __construct(private readonly Composer $messageComposer,private readonly FavouriteService $favouriteService,private readonly PaginatorInterface $paginator,private readonly InteractionService $interactionService,private readonly ListingManager $listingManager,private readonly EventDispatcherInterface $dispatcher,UserManagerInterface $fosUserManager,private readonly TranslatorInterface $translator,private readonly TranslatableListener $translatableListener,private readonly UserManager $userManager,private readonly ListingManager $listingService,private readonly ListingNoteService $listingNoteService,private readonly ListingRuleMatcher $listingRuleMatcher,private readonly LocationManager $locationManager,Setting $setting,private readonly PhoneManagerInterface $phoneManager,private readonly FeatureToggleManager $featureToggle,private readonly ListingRateService $listingRateService,TokenStorageInterface $tokenStorage,private readonly LeadService $leadManager,private readonly CallRequestManager $callRequestManager,private readonly MortgageService $mortgageService,private readonly CreditManagerInterface $creditManager,private readonly ListingExtension $listingExtension,private readonly ListingNewsFeed $listingNewsFeed,private readonly EntityManagerInterface $entityManager,private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry,) {$this->fosUserManager = $fosUserManager;$this->tokenStorage = $tokenStorage;}/*** Get Listing.** )*/#[Rest\Get('/api/v2/listing/{id}', requirements: ['id' => '\d+'], options: ['i18n' => false], name: 'aqarmap_api_get_listing_v2')]#[Rest\View(serializerGroups: ['Default', 'Details', 'Compound'])]#[Cache(expires: '+2 hours', maxage: '+2 hours', smaxage: '+2 hours', public: false, vary: ['Accept-Language', 'X-Accept-Version', 'Accept'])]#[OA\Response(response: 404, description: 'Returned when the listing is not found')]public function getListing(Listing $listing): array{return ['listing' => $listing];}/*** Listing details.*/#[Rest\Get('/api/v4/listing/{id}', requirements: ['id' => '\d+'], options: ['i18n' => false], name: 'aqarmap_api_get_listing_v4')]#[Rest\View(serializerGroups: ['listingDetails', 'listingDetailsWithLocationCompound'])]#[Cache(expires: '+6 hours', maxage: '+6 hours', smaxage: '+6 hours', public: true, vary: ['Accept-Language', 'X-Accept-Version', 'Accept'])]public function getListingDetails(int $id, ListingRepository $listingRepository, ListingManager $listingManager): array{$entityManagerFilters = $this->entityManager->getFilters();if ($entityManagerFilters->isEnabled('softdeleteable')) {$entityManagerFilters->disable('softdeleteable');}$listing = $listingRepository->find($id);$entityManagerFilters->enable('softdeleteable');return ['listing' => $listingManager->serializeListing($listing)];}/*** Get Listing Children (Project Units).** )** @return array*/#[Rest\Get('/api/v2/listing/{id}/children', options: ['i18n' => false], name: 'aqarmap_api_get_listing_children_v2')]#[Rest\View(serializerGroups: ['Default', 'Details'])]#[OA\Response(response: 404, description: 'Returned when the listing is not found')]public function getListingChildren(Listing $listing){return $this->respond($listing->getLiveChildren());}/*** @return Collection** @throws ORMException* @throws OptimisticLockException*/#[Rest\Post('/api/v2/listing/{listing}/phones', options: ['i18n' => false], name: 'aqarmap_api_get_listing_phones_v2')]#[Rest\View(serializerGroups: ['Default', 'Details'])]#[OA\Parameter(name: 'lead', in: 'query', description: '', required: false)]public function getListingPhone(Listing $listing, Request $request, ?User $user = null){$version = (float) ltrim((string) $request->headers->get('X-Accept-Version'), 'v');$leadModel = new LeadModel();$currentUrl = $request->getUri();if (str_contains($currentUrl, 'v2')) {if (!$user = $this->getUser() && $version <= 2.13) {throw new AccessDeniedHttpException();}}$form = $this->createForm(QuickLeadType::class, null, ['method' => 'POST','csrf_protection' => false,]);$form->handleRequest($request);$lead = $form->getData();$phoneManager = $this->phoneManager;$originalPhoneNumber = $lead['phone'];$phoneNumber = $phoneManager->trimZero($lead['phone'], $lead['countryCode']);$aqarmapUserService = $this->userManager;$hasEmail = !$lead['isAutoGeneratedEmail'];/** @var UserManagerInterface $userManager */$userManager = $this->fosUserManager;/** @var User $user */$user = $userManager->findUserByEmail($lead['email']);$countryCode = $lead['countryCode'];if (!$user && !$hasEmail) {$user = $aqarmapUserService->findLatestByPhone($phoneNumber, $countryCode);}if (!$user && $hasEmail) {$user = $aqarmapUserService->findAndReplaceUserAndEmail($phoneNumber, $lead['email'], $countryCode);}if (!$user) {$user = $userManager->createUser();$user->setFullName($lead['name'])->setPhoneNumber($lead['phone'])->setTempOriginalPhoneNumber($originalPhoneNumber)->setTempCountryCode($lead['countryCode'])->setEmail($lead['email'])->setHasEmail($hasEmail)->setLanguage('ar');$user = $aqarmapUserService->quickRegistration($form, $user, $request);}$phone = $phoneManager->addNewUserPhone($phoneNumber, $lead['countryCode'], $user, true, false, null, $originalPhoneNumber);$userManager->updateUser($user);$userManager->reloadUser($user);$leadModel->setPhone($phone->getPhone());$leadModel->setName($request->request->get('name', $lead['name']));$leadModel->setEmail($request->request->get('email', $lead['email']));$leadService = $this->leadManager;$leadModel->setLeadType(LeadTypes::SHOW_PHONE);$leadModel->setListing($listing);$leadModel->setSource('api');$leadModel->setUser($user);$lead = $leadService->addLead($leadModel);if ($lead) {$this->dispatcher->dispatch(new LeadEvent($listing,$user,$lead),'aqarmap.listing.show_seller_number');}return $listing->getPhones();}/*** @return bool|Form** @throws AccessDeniedHttpException*/#[Rest\Post('/api/v2/listing/{listing}/contact_seller', options: ['i18n' => false], name: 'aqarmap_api_listing_contact_seller_v2')]#[Rest\QueryParam(name: 'campaign', description: 'Campaign Name')]#[Rest\View(serializerGroups: ['Default', 'Details'])]#[OA\Parameter(name: 'campaign', in: 'query', description: 'Campaign Name', required: false)]#[OA\Parameter(name: 'contact_seller', in: 'query', description: '', required: false)]#[OA\Response(response: 404, description: 'Returned when the listing is not found')]public function contactSeller(Request $request, Listing $listing, ?User $user = null){$currentUrl = $request->getUri();if (str_contains($currentUrl, 'v2')) {if (!$user = $this->getUser()) {throw new AccessDeniedHttpException();}}$form = $this->createForm(ContactSellerFormType::class, null, ['method' => 'POST','csrf_protection' => false,]);$form->handleRequest($request);if ($form->isSubmitted() && $form->isValid()) {// Get campaign name$campaign = $request->query->get('campaign');$message = $form->getData();// Send contact seller message and send a lead$composer = $this->messageComposer;$composer->setSender($user)->compose($message['message'], $listing, $campaign);return true;}return $form;}/*** @return Response $response** @throws \Exception*/#[Rest\Post('/api/v2/listing/{listing}/contact_seller/quick', options: ['i18n' => false], name: 'aqarmap_api_listing_quick_contact_seller_v2')]#[Rest\QueryParam(name: 'campaign', description: 'Campaign Name')]#[Rest\View(serializerGroups: ['Default', 'Details'])]#[OA\Parameter(name: 'campaign', in: 'query', description: 'Campaign Name', required: false)]#[OA\Parameter(name: 'contact_seller', in: 'query', description: '', required: false)]#[OA\Response(response: 201, description: 'Returned when user created and message sent successfully')]#[OA\Response(response: 404, description: 'Returned when the listing is not found')]#[OA\Response(response: 403, description: 'Returned when parameter is missing')]public function contactSellerWithQuickRegistration(Request $request, Listing $listing): Response{if (!$this->isValidQuickContactSellerParameters($request->get('contact_seller'))) {return new Response(null, Response::HTTP_FORBIDDEN);}$form = $this->createForm(QuickContactSellerType::class, null, ['method' => 'POST','csrf_protection' => false,]);$form->handleRequest($request);if ($form->isSubmitted() && !$form->isValid()) {return new Response(null, Response::HTTP_FORBIDDEN);}$userManager = $this->userManager;$fromData = $form->getData();$user = $userManager->createQuickUser($request, $form);$campaign = $request->query->get('campaign');$composer = $this->messageComposer;$composer->setSender($user)->compose($fromData['message'], $listing, $campaign);return new Response(null, Response::HTTP_CREATED);}private function isValidQuickContactSellerParameters(array $parameters): bool{if (!\array_key_exists('email', $parameters)|| !\array_key_exists('phone', $parameters)|| !\array_key_exists('countryCode', $parameters)) {return false;}return true;}/*** @return bool|array*/#[Rest\Post('/api/v2/listing/{listing}/call_request', options: ['i18n' => false], name: 'aqarmap_api_listing_call_request_v2')]#[Rest\QueryParam(name: 'campaign', description: 'Campaign Name')]#[Rest\View(serializerGroups: ['Default', 'Details'])]public function callRequest(Listing $listing, Request $request, ?User $user = null){$version = (float) ltrim((string) $request->headers->get('X-Accept-Version'), 'v');$callRequest = new CallRequest();$currentUrl = $request->getUri();if (str_contains($currentUrl, 'v2')) {if (!$user = $this->getUser() && $version <= 2.13) {throw new AccessDeniedHttpException();}}$form = $this->createForm(QuickLeadType::class, null, ['method' => 'POST','csrf_protection' => false,]);$form->handleRequest($request);$lead = $form->getData();/** @var UserManagerInterface $userManager */$userManager = $this->fosUserManager;$aqarmapUserManager = $this->userManager;$hasEmail = !$lead['isAutoGeneratedEmail'];$phoneManager = $this->phoneManager;$originalPhoneNumber = $lead['phone'];$countryCode = $lead['countryCode'];$phoneNumber = $phoneManager->trimZero($lead['phone'], $countryCode);/** @var User $user */$user = $userManager->findUserByEmail($lead['email']);if (!$user && !$hasEmail) {$user = $aqarmapUserManager->findLatestByPhone($phoneNumber, $countryCode);}if (!$user && $hasEmail) {$user = $aqarmapUserManager->findAndReplaceUserAndEmail($phoneNumber, $lead['email'], $countryCode);}if (!$user) {$user = $userManager->createUser();$user->setFullName($lead['name'])->setPhoneNumber($lead['phone'])->setTempOriginalPhoneNumber($originalPhoneNumber)->setTempCountryCode($lead['countryCode'])->setEmail($lead['email'])->setHasEmail($hasEmail)->setLanguage('ar');$user = $aqarmapUserManager->quickRegistration($form, $user, $request);}$phone = $phoneManager->addNewUserPhone($phoneNumber, $lead['countryCode'], $user, true, false, null, $originalPhoneNumber);$userManager->updateUser($user);$userManager->reloadUser($user);$callRequest->setPhone($phone->getPhone());$callRequest->setLeadFullName($request->request->get('name'));$callRequest->setLeadEmail($request->request->get('email'));$campaign = $request->request->get('campaign');$callRequest->setUser($user);$callRequest->setListing($listing);$this->callRequestManager->submitCallRequest($callRequest, $campaign);return true;}/*** Add New User Listing.** @return View|Form*/#[Rest\Post('/api/v2/listing', options: ['i18n' => false], name: 'aqarmap_api_add_listing')]#[IsGranted(attribute: 'ROLE_USER')]#[OA\Parameter(name: 'listing', in: 'query', description: '', required: false)]#[OA\Response(response: 201, description: 'Returned when successfully created')]#[OA\Response(response: 400, description: 'Returned when validation error')]public function addListing(Request $request){$form = $this->createForm(ListingApiType::class, $listing = new Listing(), ['csrf_protection' => false,]);$form->handleRequest($request);if ($form->isSubmitted() && $form->isValid()) {if ($this->getUser()->getPhoneNumber()) {$listing->addPhone(new ListingPhone($this->getUser()->getPhoneNumber()));}$listingManager = $this->listingService;$listing->setUser($this->getUser());$listing->setStatus(ListingStatus::DRAFT);$listingManager->saveListing($listing);$listingEvent = new ListingEvent($listing);$this->dispatcher->dispatch($listingEvent, 'aqarmap.listing.submitted');return View::create()->setData($listing)->setStatusCode(Response::HTTP_CREATED);}return View::create()->setData($form)->setStatusCode(Response::HTTP_BAD_REQUEST);}/*** Create Single Listing Note.*/#[Rest\Post('/api/v2/listing/{id}/note', options: ['i18n' => false])]#[Rest\RequestParam(name: 'body', description: 'Note Body')]#[Rest\RequestParam(name: 'source', description: 'Supported sources: 1 = Website, 2 = Consumer App, 3 = Live App')]#[Rest\RequestParam(name: 'last_modified_at', description: 'dateTime')]#[Rest\RequestParam(name: 'created_at', description: 'dateTime')]#[IsGranted(attribute: 'ROLE_USER')]public function postListingSingleNote(Request $request,#[MapEntity(id: 'listing')]Listing $listing,ListingNoteRepository $listingNoteRepository,EntityManagerInterface $em,) {$currentUser = $this->getUser();$listingNote = $listingNoteRepository->findOneBy(['user' => $currentUser,'listing' => $listing,]);if (empty($listingNote)) {$listingNote = new ListingNote();$listingNote->setUser($currentUser);$listingNote->setListing($listing);$listingNote->setCreatedAt(new \DateTime());}$form = $this->createForm(ListingNoteType::class, $listingNote);$form->handleRequest($request);if ($form->isSubmitted() && $form->isValid()) {$em->persist($listingNote);$em->flush();return $this->json(['message' => 'Successfully submitted',], Response::HTTP_CREATED);}return $this->json(['message' => 'Error occurred',], Response::HTTP_BAD_REQUEST);}/*** Create Bulk Listing Note.** @return array*/#[Rest\Post('/api/listing/note', options: ['i18n' => false])]#[Rest\Post('/api/v2/listing/note', options: ['i18n' => false])]#[IsGranted(attribute: 'ROLE_USER')]public function postListingBulkNote(Request $request){$listingNoteService = $this->listingNoteService;$listingNoteService->addBulk($request);return $this->respond('Note Added Successfully!');}/*** Update Listing.** @return View|Form*/#[Rest\Post('/api/v2/listing/{id}', options: ['i18n' => false], name: 'aqarmap_api_update_listing')]#[Rest\View]#[IsGranted(attribute: 'ROLE_OWNER', subject: 'listing')]#[OA\Parameter(name: 'listing', in: 'query', description: '', required: false)]#[OA\Response(response: 200, description: 'Returned when successfully Updated')]#[OA\Response(response: 400, description: 'Returned when validation error')]public function updateListing(Listing $listing, Request $request){$listing->clearAttributes();$form = $this->createForm(ListingApiType::class, $listing, ['csrf_protection' => false,]);$form->handleRequest($request);if ($form->isSubmitted() && $form->isValid()) {if ($this->getUser()->getPhoneNumber()) {$listing->addPhone(new ListingPhone($this->getUser()->getPhoneNumber()));}$listingManager = $this->listingService;$listingManager->saveListing($listing);$listingEvent = new ListingEvent($listing);$this->dispatcher->dispatch($listingEvent, 'aqarmap.listing.submitted');return View::create()->setData($listing)->setStatusCode(Response::HTTP_OK);}return View::create()->setData($form)->setStatusCode(Response::HTTP_BAD_REQUEST);}/*** Upload listing photos.** @return View|array*/#[Rest\Post('/api/v2/listing/{id}/photos', options: ['i18n' => false], name: 'aqarmap_api_upload_listing_photos')]#[IsGranted(attribute: 'ROLE_OWNER', subject: 'listing')]#[OA\Parameter(name: 'photos', in: 'query', description: '', required: false)]#[OA\Response(response: 201, description: 'Returned when successfully created')]#[OA\Response(response: 400, description: 'Returned when validation error')]public function upload(Request $request, Listing $listing){$form = $this->createForm(PhotoType::class, null, ['csrf_protection' => false,]);$form->handleRequest($request);if ($form->isSubmitted() && $form->isValid()) {$outputFiles = [];$listingManager = $this->listingService;$maxOrder = $listingManager->getMaxListingPhotoOrder($listing);foreach ($form->get('file')->getData() as $index => $file) {$photo = new Photo();$photo->setFile($file);$listingPhoto = new ListingPhoto();$listingPhoto->setFile($photo);$listingPhoto->setCaption($photo->getFile()->getClientOriginalName());$listingPhoto->setOrder($maxOrder + $index + 1);$outputFiles[] = $listingPhoto;$listing->addPhoto($listingPhoto);}$listingEvent = new ListingEvent($listing);$this->dispatcher->dispatch($listingEvent, 'aqarmap.listing.submitted');$listingManager->saveListing($listing);return View::create()->setData($outputFiles)->setStatusCode(Response::HTTP_CREATED);}return ['listing' => $listing,'form' => $form->createView(),];}/*** Delete Listing entity.*/#[Rest\Delete('/api/v2/listing/{listing}', options: ['i18n' => false], name: 'aqarmap_api_delete_listing')]#[IsGranted(attribute: 'ROLE_OWNER', subject: 'listing')]#[OA\Response(response: 204, description: 'Returned when successfully Deleted')]#[OA\Response(response: 404, description: 'Returned when Listing is not found')]#[OA\Response(response: 403, description: 'Returned when you are trying to remove listing that not yours.')]public function delete(Listing $listing){if (\in_array('ROLE_PREVENT_DELETE_LISTING', $this->getUser()->getRoles())) {throw new AccessDeniedHttpException("Forbidden, user don't have this permession.");}$listingManager = $this->listingService;$listingManager->remove($listing, ListingStatus::USER_DELETED);return new Response(null, Response::HTTP_NO_CONTENT);}/*** Undelete Listing entity.** @return array*/#[IsGranted(attribute: 'ROLE_OWNER', subject: 'listing')]#[Rest\View]#[Rest\Get('/api/v2/listing/{listing}/undelete', name: 'aqarmap_api_undelete_listing', options: ['i18n' => false])]#[OA\Response(response: 200, description: 'Returned when successfully undeleted')]#[OA\Response(response: 404, description: 'Returned when Listing is not found or is not on user deleted state')]#[OA\Response(response: 403, description: 'Returned when you are trying to remove listing that not yours.')]public function undelete(Listing $listing){if (ListingStatus::USER_DELETED != $listing->getStatus()) {throw $this->createNotFoundException('Unable to find this listing.');}$listingManager = $this->listingService;$listingManager->changeStatus($listing, ListingStatus::PENDING);$listingEvent = new ListingEvent($listing);$this->dispatcher->dispatch($listingEvent, 'aqarmap.listing.resubmitted');return ['listing' => $listing];}/*** Republish Listing entity.** @return array*/#[IsGranted(attribute: 'ROLE_OWNER', subject: 'listing')]#[Rest\View]#[OA\Response(response: 200, description: 'Returned when successfully Republished')]#[OA\Response(response: 404, description: 'Returned when Listing is not found or is not on Expired state')]#[OA\Response(response: 403, description: 'Returned when you are trying to Republish listing that not yours.')]#[Rest\Get('/api/v2/listing/{listing}/relist', name: 'aqarmap_api_republish_listing', options: ['i18n' => false])]public function relist(Listing $listing){if (ListingStatus::EXPIRED != $listing->getStatus()) {throw $this->createNotFoundException('Unable to find this listing.');}$em = $this->managerRegistry->getManager();$listingRepo = $em->getRepository(Listing::class);$relistChild = $listingRepo->getRelistChild($listing);// If the expired listing already republished before .. return the republished version.if ($relistChild) {return ['listing' => $relistChild];}$listingManager = $this->listingService;$listing = $listingManager->relist($listing);$listingEvent = new ListingEvent($listing);$this->dispatcher->dispatch($listingEvent, 'aqarmap.listing.resubmitted');return ['listing' => $listing];}/*** Feature A Listing.** @return View*/#[Rest\Post('/api/v2/listing/{listing}/feature', options: ['i18n' => false], name: 'aqarmap_api_feature_listing')]#[Rest\RequestParam(name: 'type', description: 'Feature Type, 1 for payment, 2 for making it featured')]#[IsGranted(attribute: 'ROLE_OWNER', subject: 'listing')]public function feature(Listing $listing, Request $request){$type = $request->get('type');if (!$type) {throw new BadRequestHttpException('Please Specify the feature type');}$matcher = $this->listingRuleMatcher;$listingRule = $matcher->match($listing);$translator = $this->translator;$featuredDuration = $listingRule['featured_duration'];$featuredFees = $listingRule['featured_fees'];if (ListingFeatures::FEATURED == $type) {// Check if there is a value for featured_duration & featured_feesif (empty($featuredDuration) || empty($featuredFees)) {throw new LogicHttpException($translator->trans('Making a listing Featured is not available for this listing.', [], 'exceptions'));}try {$this->listingService->makeItFeatured($listing,['featuredFees' => $featuredFees,'featuredDuration' => $featuredDuration,'listingFeaturedType' => ListingFeaturedTypes::FEATURED,'listingFeature' => $type,]);} catch (\Exception $e) {throw new LogicHttpException($translator->trans($e->getMessage(), [], 'exceptions'));}} elseif (ListingFeatures::PAID == $type) {/** @var \Aqarmap\Bundle\CreditBundle\Services\CreditManager $creditManager */$creditManager = $this->creditManager;if ($listing->getPublicationCredit()) {throw new LogicHttpException($translator->trans('credit.already_paid'));} elseif ($listingRule['publication_fees'] > $creditManager->getBalance($listing->getUser())) {throw new LogicHttpException($translator->trans('credit.not_enough_credits'));}// Subtract publication fees$credits = $creditManager->deduction($listing->getUser(), $listingRule['publication_fees'], 'Listing Fees', CreditStatus::PENDING);foreach ($credits as $credit) {if ($credit instanceof Credit) {$this->listingService->addFeature($listing, ListingFeatures::PAID, null, $credit);}}$listingEvent = new ListingEvent($listing);$this->dispatcher->dispatch($listingEvent, 'aqarmap.listing.submitted');}return View::create()->setData(['listing' => $listing]);}// -------------------------------------------------------------------------//// ++++++++++++++ Quick contact seller & call request Actions +++++++++++++++//// -------------------------------------------------------------------------///*** ).** @return bool|Form|array** @throws AccessDeniedHttpException** ===============================================================* |* | DEPRECATED: USE QuickCreateLeadAction::LeadController instead* |* ===============================================================*/#[Rest\Post('/api/v2/listing/{listing}/quick_create_lead', options: ['i18n' => false], name: 'aqarmap_api_listing_quick_create_lead')]#[Rest\QueryParam(name: 'campaign', description: 'Campaign Name')]#[Rest\QueryParam(name: 'country_code', default: null, description: 'Country Code')]#[Rest\View(serializerGroups: ['Default', 'Details'])]#[OA\Response(response: 404, description: 'Returned when the listing is not found')]public function quickCreateLead(Request $request, Listing $listing){$form = $this->createForm(QuickCreateLeadFormType::class, null, ['method' => 'POST','csrf_protection' => false,]);$form->handleRequest($request);if ($form->isSubmitted() && $form->isValid()) {$lead = $form->getData();/** @var UserManagerInterface $userManager */$userManager = $this->fosUserManager;$leadModel = new LeadModel();try {/** @var User $user */if ($user = $userManager->findUserByEmail($form->get('email')->getData())) {$phone = $this->phoneManager->addNewUserPhone($lead['phoneNumber'],'+'.str_replace(' ', '', $request->get('country_code')),$user,true,true,CountryCodes::getCountryFromCodeNumber('+'.str_replace(' ', '', $request->get('country_code'))),$lead['phoneNumber']);$userManager->updateUser($user);$leadModel->setUser($user);} else {$user = $userManager->createUser();$user->setFullName($lead['fullName'])->setEmail($lead['email'])->setLanguage($request->getLocale());$user = $this->userManager->quickRegistration($form, $user, $request);$phone = $this->phoneManager->addNewUserPhone($lead['phoneNumber'],'+'.str_replace(' ', '', $request->get('country_code')),$user,true,true,CountryCodes::getCountryFromCodeNumber('+'.str_replace(' ', '', $request->get('country_code'))),$lead['phoneNumber']);$userManager->reloadUser($user);$leadModel->setUser($user);}} catch (\Exception) {return ['status' => 'error','message' => 'Cannot create a user.',];}if ($user) {try {$leadModel->setEmail($lead['email']);$leadModel->setName($lead['fullName']);$leadModel->setPhone($phone->getPhone());$leadModel->setCampaign($request->request->get('campaign'));$leadModel->setMessage($form->get('message')->getData());$leadModel->setListing($listing);$this->listingService->quickCreateLead($leadModel);return ['status' => 'ok',];} catch (\Exception) {return ['status' => 'error','message' => 'Cannot create the lead.',];}}}return $form;}/*** Get Listing Rules.** )** @return array*/#[Rest\Get('/api/v2/listing/{id}/rules', options: ['i18n' => false], name: 'aqarmap_api_get_listing_rules')]#[IsGranted(attribute: 'ROLE_OWNER', subject: 'listing')]#[Rest\View]#[OA\Response(response: 404, description: 'Returned when the listing is not found')]public function getListingRules(Listing $listing){$listingMatcher = $this->listingRuleMatcher;$rules = $listingMatcher->match($listing);return ['rules' => $rules];}/*** Get Listing Note.** @return array*/#[Rest\Get('/api/v2/listing/{id}/note', options: ['i18n' => false], name: 'aqarmap_api_get_listing_note')]#[IsGranted(attribute: 'ROLE_USER')]#[Rest\View]#[OA\Parameter(name: 'propertyType', in: 'query', description: 'propertyType of the unites', required: false)]#[OA\Parameter(name: 'section', in: 'query', description: 'Section of the unites', required: false)]#[OA\Response(response: 404, description: 'Returned when the listing is not found')]public function getListingNote(Listing $listing){/** @var EntityManager $em */$em = $this->managerRegistry->getManager();$listingNoteRepo = $em->getRepository(ListingNote::class);return $listingNoteRepo->findOneBy(['user' => $this->getUser(),'listing' => $listing,]);}/*** Get account statistics (Listings Statistics).** @throws \Exception*/#[Rest\Get('/api/user/listings/statistics', requirements: ['id' => '\d+'], options: ['expose' => true, 'i18n' => false], name: 'aqarmap_api_get_user_listings_rates_counts')]#[Rest\Post('/api/v2/user/listings/statistics', options: ['i18n' => false], name: 'aqarmap_api_get_user_listings_rates_counts_v2')]#[Rest\View(serializerGroups: ['Default', 'Details'])]#[IsGranted(attribute: 'ROLE_USER')]public function getUserListingsPerformanceStatistics(Request $request,#[MapEntity(id: 'user')]?User $user = null,): View {$isSubAccount = null != $user && $this->getUser() === $user->getParent();$startDate = null;$period = $request->query->get('period', null);if (!$this->getUser() && !$isSubAccount) {throw new AccessDeniedHttpException();}$user = $user ?: $this->getUser();if ('7Days' == $period) {$startDate = date('Y-m-d', strtotime('-7 days'));} elseif ('30Days' == $period) {$startDate = date('Y-m-d', strtotime('-30 days'));}return $this->respond(['rates' => $this->listingService->getUserListingsPerformanceStatistics($user, $startDate, $period),]);}/*** Generates XML file for Listings News feed for marketing purpose.** @return Response*/#[Rest\Get('/api/listings/feed/{platform}', options: ['i18n' => false], name: 'aqarmap_api_get_listings_news_feed', defaults: ['page' => 1])]#[Rest\View(serializerGroups: ['Default', 'Details'])]public function getNewsFeed($platform, Request $request){$criteria = [];$page = min($request->query->getInt('page') ?: 1, ListingNewsFeed::MAX_PAGES);$limit = min($request->query->getInt('limit', 100), 100);if ((int) $request->query->getInt('page', 1) > $page) {throw new NotFoundHttpException('Exceeded maximum number of pages');}if ($request->query->get('ugc')) {$data = explode(',', $request->query->get('ugc'));$data = array_map('intval', $data);$criteria['groupCategory'] = array_filter($data);}if ($request->query->get('ug')) {$data = explode(',', $request->query->get('ug'));$data = array_map('intval', $data);$criteria['userGroup'] = array_filter($data);}if ($request->query->get('hl')) {$criteria['locale'] = $request->query->get('hl');}if ($request->query->get('maxlead')) {$criteria['maxlead'] = $request->query->get('maxlead');}if ($request->query->get('feat')) {$criteria['feat'] = $request->query->get('feat');}if ($request->query->get('location')) {$locations = $this->locationManager->buildLocationsArrayByParentId($request->query->get('location'));if (!empty($locations)) {$criteria['locations'] = $locations;}}if ($request->query->get('section')) {$data = explode(',', $request->query->get('section'));$data = array_map('intval', $data);$criteria['sections'] = array_filter($data);}$news = $this->listingNewsFeed;$offset = ($page - 1) * $limit;$excludedKeys = ['sections', 'maxlead'];if (!array_diff(array_keys($criteria), $excludedKeys)) {throw new BadRequestHttpException(sprintf('Invalid Request: Please add at least one additional filter besides %s.', implode(', ', $excludedKeys)));}if ('json' == $request->query->get('format')) {return $this->responseJson($news->asJson($offset, $limit, $criteria));}return $this->responseXml($news->asXml($offset, $limit, $platform, $criteria));}/*** Stateful endpoint to update listing fields.*/#[Rest\Post('api/listing/{id}/edit', name: 'update_listing_field', options: ['i18n' => false, 'expose' => true])]#[Rest\View(serializerGroups: ['Default', 'Details'])]#[IsGranted(attribute: 'ROLE_EDIT_REVIEW_LISTINGS')]#[IsGranted(attribute: new Expression('is_granted("ROLE_ADMIN") or is_granted("ROLE_OWNER", subject["listing"])'), subject: ['listing'])]public function update(Listing $listing, Request $request): View{$listingManager = $this->listingManager;$isEnabled = $this->featureToggle->isEnabled('web.mortgage.options');$mortgage = $request->get('eligibleForMortgage');$fields = $request->request->all();if ($isEnabled) {if (true == $mortgage) {$this->mortgageService->addEligibleMortgageTypes($listing);} else {$listingManager->setEligibleMortgageToNull($listing);}unset($fields['eligibleForMortgage']);}$this->dispatcher->dispatch(new ListingUpdatedEvent($listing, $fields),ListingUpdatedEvent::UPDATED);$listing = $listingManager->update($listing, $fields);if (ListingStatus::LIVE == $listing->getStatus()) {$listingManager->changeStatus($listing, ListingStatus::PENDING);}return $this->respond(['listing' => $listing,'msg' => 'Listing was updated successfully',]);}/*** Stateful endpoint to get Photos Of The Given Listings.*/#[Rest\Get('/api/listings/{listing}/photos', name: 'aqarmap_api_get_listing_photos', options: ['expose' => true, 'i18n' => false])]#[IsGranted(attribute: new Expression('is_granted("ROLE_ADMIN") or is_granted("ROLE_OWNER", subject["listing"])'), subject: ['listing'])]public function getListingPhotos(Listing $listing): View{$listingPhotos = $listing->getPhotos();$listingPhotosData = [];foreach ($listingPhotos as $listingPhoto) {$listingPhotosData[] = ['id' => $listingPhoto->getId(),'type' => $listingPhoto->getType(),'caption' => $listingPhoto->getCaption(),'file' => $listingPhoto->getFile(),];}return $this->respond($listingPhotosData);}/*** Get Photo Types.** @return Response*/#[Rest\Get('/api/listings/photoTypes', name: 'aqarmap_api_get_listing_photo_types', options: ['expose' => true, 'i18n' => false])]public function getPhotoTypes(){return $this->respond(PhotoTypes::getPhotoTypes());}/*** Upload Listing Photo.** @return array** @throws OptimisticLockException*/#[Rest\Post('/api/listing/{id}/upload_photo', name: 'aqarmap_api_admin_upload_listing_photo', options: ['expose' => true, 'i18n' => false])]public function uploadListingPhoto(Listing $listing){$outputFiles = [];$listingManager = $this->listingManager;$maxOrder = $listingManager->getMaxListingPhotoOrder($listing);$photo = new Photo();$file = $_FILES['file'];$file = new UploadedFile($file['tmp_name'], $file['name'], $file['type']);$photo->setFile($file);$listingPhoto = new ListingPhoto();$listingPhoto->setFile($photo);$listingPhoto->setCaption($photo->getFile()->getClientOriginalName());$listingPhoto->setOrder($maxOrder + 1);$outputFiles[] = $listingPhoto;$listing->addPhoto($listingPhoto);$em = $this->managerRegistry->getManager();$em->persist($listing);$em->flush();return $this->respond(['status' => 'OK']);}/*** Gets rate details of listing.*/#[Rest\Get('/api/v2/listing/{listing}/rates', options: ['i18n' => false])]#[Rest\Get('/api/listing/{listing}/rates', options: ['expose' => true, 'i18n' => false], name: 'aqarmap_api_listing_rates_details')]#[Rest\View(serializerGroups: ['Rates'])]#[Cache(expires: '+1 week', maxage: '+1 week', smaxage: '+1 week', public: false, vary: ['Accept-Language', 'X-Accept-Version', 'Accept'])]public function getRateDetails(Listing $listing){return $this->respond(current($this->listingRateService->getRatesDetails([$listing])));}/*** @return View*/#[Rest\Get('/api/v2/listing/{listing}/preview', options: ['i18n' => false])]#[Rest\Get('/api/listing/{listing}/preview', options: ['expose' => true, 'i18n' => false], name: 'aqarmap_api_listing_preview_data')]#[Rest\View(serializerGroups: ['Preview'])]public function getListingPreviewData(Listing $listing){return $this->respond($listing);}/*** @return JsonResponse** @throws \Exception*/#[Rest\Get('/api/listing/{listing}/lead-analytics', options: ['i18n' => false, 'expose' => true], name: 'aqarmap_api_listing_lead_analytics')]public function getLeadAnalytics(Listing $listing){$analytics = $this->listingService->getLeadAnalytics($listing);return $this->respond($analytics);}/*** @return string*/#[Rest\Delete('/api/v2/listing/{listing}/favourite', options: ['i18n' => false])]#[Rest\Delete('/api/listing/{listing}/favourite', options: ['i18n' => false], name: 'aqarmap_api_remove_favourite_listing')]#[IsGranted(attribute: 'ROLE_USER')]public function deleteFavourite(Request $request){$this->favouriteService->deleteByListing($request->attributes->get('listing'));return $this->respond('Favourite Deleted Successfully!');}/*** @return string*/#[Rest\Delete('/api/v2/listing/{listing}/note', options: ['i18n' => false])]#[Rest\Delete('/api/listing/{listing}/note', options: ['i18n' => false], name: 'aqarmap_api_remove_listing_note')]#[IsGranted(attribute: 'ROLE_USER')]public function deleteNote(Request $request){$this->listingNoteService->deleteByListing($request->attributes->get('listing'));return $this->respond('Note Deleted Successfully!');}#[Rest\Post('/api/listing/{listing}/rate-review', options: ['i18n' => false, 'expose' => true], name: 'aqarmap_api_change_listing_rate_review_status')]#[IsGranted(attribute: 'ROLE_ADMIN')]public function rateReview(Request $request, Listing $listing): View{$this->listingService->updateIsRateReviewed($listing, $request->request->get('isReviewed'));return $this->respond('Listing rate review status changed successfully');}#[Rest\Get('/api/listing/{id}/similar_listings_count', options: ['i18n' => false, 'expose' => true], name: 'aqarmap_api_get_similar_listings_count')]#[Rest\View(serializerGroups: ['Default', 'Details'])]#[OA\Response(response: 404, description: 'Returned when the listing is not found')]public function getSimilarListingsCount(Listing $listing): int{$listingManager = $this->listingService;try {$count = $listingManager->countSimilarListings($listing);} catch (\Exception $exception) {echo $exception->getMessage();$this->errorLogger->error(shell_exec("Error retriving similar listings count! {$exception->getMessage()}"));$count = 0;}return $count;}#[Rest\Get('/api/v2/listings/notes', options: ['i18n' => false])]#[Rest\Get('/api/listings/notes', name: 'aqarmap_api_listing_notes_and_favourites', options: ['i18n' => false])]#[Rest\QueryParam(name: 'from', description: '[favourites|notes]')]#[Rest\View(serializerGroups: ['Default', 'List'])]public function getListingsWithNotes(Request $request): array{$listingsIdWithNotes = [];if ($content = $request->getContent()) {$listingsIdWithNotes = json_decode($content, true);}$listings = $this->managerRegistry->getRepository(Listing::class)->getByIdsQuery(array_column($listingsIdWithNotes, 'listingId'));$pagination = $this->paginator->paginate($listings,$request->query->getInt('page', 1),$request->query->getInt('limit', 10));$data = $this->listingService->setStaticNotesFromPaginator($pagination, $listingsIdWithNotes);if ('favourites' == $request->query->get('from')) {return ['favourite' => $data];}return ['note' => $data];}/*** ApiDoc(* resource = true,* section = "Listing",* description="Get ApprovalRejectionWaitingTime",* statusCodes={* 200="Returned when successful",* }* ).** @return array** @throws \Exception*/#[Rest\Get('/api/listing/arwt', options: ['i18n' => false], name: 'aqarmap_api_listing_arwt')]#[Rest\View]public function getApprovalRejectionWaitingTime(Request $request){$criteria = array_merge($request->query->all(),['actionTime' => true]);$listingManager = $this->listingService;$listingExtension = $this->listingExtension;return ['arwt' => $listingExtension->waited($listingManager->getApprovalRejectionWaitingTime($criteria)),];}/*** filter Listing Children with property type (Project Units).** @return array*/#[Rest\Get('/api/listing/{id}/children', name: 'aqarmap_api_get_listing_children', options: ['i18n' => false])]#[Rest\QueryParam(name: 'propertyType', description: 'propertyType of the unites')]#[Rest\View(serializerGroups: ['UnitDetails'])]#[OA\Parameter(name: 'propertyType', description: 'propertyType of the unites', in: 'query', required: false)]#[OA\Response(response: 404, description: 'Returned when the listing is not found')]public function filterListingChildren(Listing $listing,#[MapEntity(id: 'propertyType')]?PropertyType $propertyType = null,): View {if (!$this->getUser()) {throw new AccessDeniedHttpException();}return $this->respond($listing->getLiveChildren($propertyType));}/*** API for cloning listing.*/#[Rest\Post('/api/listing/{id}/clone', name: 'aqarmap_api_clone_listing_children', options: ['i18n' => false])]#[Rest\Post('/api/v2/listing/{id}/clone', name: 'aqarmap_api_v2_clone_listing_children', options: ['i18n' => false])]#[Rest\RequestParam(name: 'propertyType', description: 'propertyType of the unites')]#[Rest\RequestParam(name: 'section', description: 'Section of the unites')]#[IsGranted(attribute: 'IS_AUTHENTICATED_REMEMBERED')]#[Rest\View(serializerGroups: ['UnitDetails'])]#[OA\Parameter(name: 'propertyType', description: 'propertyType of the unites', in: 'query', required: false)]#[OA\Parameter(name: 'section', description: 'Section of the unites', in: 'query', required: false)]#[OA\Response(response: 404, description: 'Returned when the listing, propertyType or section is not found')]public function cloneListing(#[MapEntity(expr: 'repository.find(request.get("section"))', stripNull: false)]?Section $section,#[MapEntity(expr: 'repository.find(request.get("propertyType"))', stripNull: false)]?PropertyType $propertyType,Listing $sourceListing): View {$syncedFields = $sourceListing->getParent() ? ListingSyncedFields::UNIT_FIELDS : ListingSyncedFields::PARENT_FIELDS;try {$listing = $this->listingManager->initializeListingForCloning($sourceListing, $propertyType, $section, $this->getUser());$this->listingManager->syncListingForProject(['targetListingId' => $listing->getId(),'sourceListingId' => $sourceListing->getId(),'syncedFields' => $syncedFields,'targetListingSavedAt' => $listing->getUpdatedAt()->getTimestamp(),]);return $this->respond($listing->getId());} catch (\Exception $exception) {throw new \Exception('Could not clone listing', null, $exception);}}#[Rest\Get('/api/v2/listing/{listing}/top-seller', options: ['i18n' => false], name: 'aqarmap_api_listing_top_seller')]#[Rest\View(serializerGroups: ['TopCustomers'])]public function getListingTopSeller(Listing $listing, TopSellerRetrievalService $topSellerRetrievalService){$topSeller = new TopSeller();$topSeller->setLocation($listing->getLocation()->getId());$topSeller->setSection($listing->getSection()->getId());$topSeller->setPropertyType($listing->getPropertyType()->getId());return $topSellerRetrievalService->getTopSellerPersonalData($topSeller);}#[Rest\Get('/api/v2/listing/{listing}/related-listings', name: 'aqarmap_api_listing_related_listings', options: ['i18n' => false])]#[Cache(expires: '+1 days', maxage: '+1 days', smaxage: '+1 days', public: true, vary: ['Accept-Language', 'X-Accept-Version', 'Accept'])]#[Rest\View(serializerGroups: ['RelatedListingsV2'])]public function getRelatedListing(Listing $listing, ListingManager $listingManager, ListingRepository $listingRepository){try {$relatedListingsElasticResponse = $listingManager->getRelatedListingsElasticResponse($listing);$listingsQueryBuilder = $listingRepository->getListingsByIds(array_column($relatedListingsElasticResponse['items'], 'id'));return $listingsQueryBuilder->getQuery()->getResult();} catch (\Exception $exception) {throw new \Exception('Could not get related listings', null, $exception);}}#[Rest\Post('/api/v2/listing/{id}/views', name: 'aqarmap_api_add_listing_views', requirements: ['id' => '\d+'], options: ['i18n' => false])]#[Rest\View]public function increaseListingViews(Listing $listing): array{try {$this->interactionService->increaseViews($listing, $this->getUser());return ['status' => 'ok','message' => 'Views Created Successfully!',];} catch (\Exception $exception) {throw new \Exception('Could not increase listing views', null, $exception);}}}