src/Aqarmap/Bundle/DiscussionBundle/Controller/DiscussionController.php line 267

  1. <?php
  2. namespace Aqarmap\Bundle\DiscussionBundle\Controller;
  3. use App\Exception\BadRequestHttpException;
  4. use Aqarmap\Bundle\DiscussionBundle\Constant\DiscussionSearchTabs;
  5. use Aqarmap\Bundle\DiscussionBundle\Constant\DiscussionStatus;
  6. use Aqarmap\Bundle\DiscussionBundle\Entity\Comment;
  7. use Aqarmap\Bundle\DiscussionBundle\Entity\Discussion;
  8. use Aqarmap\Bundle\DiscussionBundle\Form\CommentType;
  9. use Aqarmap\Bundle\DiscussionBundle\Form\DiscussionSearchType;
  10. use Aqarmap\Bundle\DiscussionBundle\Form\DiscussionType;
  11. use Aqarmap\Bundle\DiscussionBundle\Repository\CategoryRepository;
  12. use Aqarmap\Bundle\DiscussionBundle\Repository\CommentRepository;
  13. use Aqarmap\Bundle\DiscussionBundle\Repository\DiscussionRepository;
  14. use Aqarmap\Bundle\DiscussionBundle\Service\DiscussionManager;
  15. use Aqarmap\Bundle\DiscussionBundle\Service\WordpressManager;
  16. use Aqarmap\Bundle\ListingBundle\Entity\Location;
  17. use Aqarmap\Bundle\ListingBundle\Form\QuickLeadType;
  18. use Aqarmap\Bundle\ListingBundle\Repository\ListingRepository;
  19. use Aqarmap\Bundle\ListingBundle\Repository\LocationRepository;
  20. use Aqarmap\Bundle\ListingBundle\Repository\SectionRepository;
  21. use Aqarmap\Bundle\ListingBundle\Service\LocationManager;
  22. use Aqarmap\Bundle\ListingBundle\Service\SupplyDemandManager;
  23. use Aqarmap\Bundle\MainBundle\Form\ConfirmFormType;
  24. use Aqarmap\Bundle\NotificationBundle\DatabaseNotification;
  25. use Aqarmap\Bundle\UserBundle\Entity\User;
  26. use Aqarmap\Bundle\UserBundle\Form\QuickRegistrationFormType;
  27. use Aqarmap\Bundle\UserBundle\Repository\UserInterestRepository;
  28. use Aqarmap\Bundle\UserBundle\Services\UserInterestManager;
  29. use Aqarmap\Bundle\UserBundle\Services\UserManager;
  30. use Doctrine\ORM\EntityManagerInterface;
  31. use Knp\Component\Pager\PaginatorInterface;
  32. use Predis\Client;
  33. use Psr\Log\LoggerInterface;
  34. use Psr\Log\LogLevel;
  35. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  36. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  37. use Symfony\Component\Form\FormInterface;
  38. use Symfony\Component\HttpFoundation\RedirectResponse;
  39. use Symfony\Component\HttpFoundation\Request;
  40. use Symfony\Component\HttpFoundation\Response;
  41. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  42. use Symfony\Component\Routing\Attribute\Route;
  43. use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface;
  44. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  45. use Symfony\Contracts\Translation\TranslatorInterface;
  46. /**
  47. * Discussion controller.
  48. */
  49. class DiscussionController extends AbstractController
  50. {
  51. public function __construct(
  52. private readonly DiscussionManager $discussionManager,
  53. private readonly WordpressManager $wordpressManager,
  54. private readonly UserInterestRepository $userInterestRepository,
  55. private readonly UserInterestManager $userInterestManager,
  56. private readonly EntityManagerInterface $em,
  57. Client $redisClient,
  58. private readonly LocationRepository $locationRepository,
  59. private readonly DiscussionRepository $discussionRepository,
  60. private readonly ListingRepository $listingRepository,
  61. private readonly SectionRepository $sectionRepository,
  62. private readonly CategoryRepository $categoryRepository,
  63. TokenStorageInterface $tokenStorage,
  64. private readonly SupplyDemandManager $supplyDemandManager,
  65. private readonly ParameterBagInterface $parameterBag,
  66. private readonly TranslatorInterface $translator,
  67. private readonly PaginatorInterface $paginator,
  68. private readonly UserManager $userManager,
  69. private readonly CommentRepository $commentRepository,
  70. EventDispatcherInterface $eventDispatcher,
  71. LoggerInterface $logger,
  72. private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry,
  73. ) {
  74. }
  75. /**
  76. * Lists all Discussion entities.
  77. */
  78. #[Route(path: 'ask-neighbors/', name: 'neighborhood_discussion_list', methods: ['GET'])]
  79. #[Route(path: 'discussion/', name: 'discussion_list', methods: ['GET'])]
  80. #[Route(path: 'question/', name: 'neighborhood_question_discussion_list', methods: ['GET'])]
  81. public function discussions(Request $request, LocationManager $locationManager)
  82. {
  83. $response = $this->removePageQuery($request);
  84. if ($response instanceof RedirectResponse) {
  85. return $response;
  86. }
  87. if ('discussion_list' == $request->get('_route')) {
  88. return $this->redirectToRoute('neighborhood_discussion_list', $request->query->all(), Response::HTTP_MOVED_PERMANENTLY);
  89. }
  90. $this->managerRegistry->getManager();
  91. $user = $this->getUser();
  92. $userInterests = [];
  93. if ($user instanceof User) {
  94. $userInterests = $this->userInterestRepository->getUserInterests($user, 4)->getQuery()->getResult();
  95. }
  96. $userInterests = !empty($userInterests) ? $userInterests : [$this->userInterestManager->getListingsMayBeInterested()];
  97. $interestsListings = $this->discussionManager->getInterestsListings($userInterests);
  98. $location = $request->query->get('location') ? $this->locationRepository->find($request->query->get('location')) : null;
  99. $discussion = new Discussion();
  100. $discussionForm = $form = $this->createForm(DiscussionType::class, $discussion, [
  101. 'action' => $this->generateUrl('neighborhood_discussion_create'),
  102. 'method' => 'POST',
  103. 'flatLocations' => [],
  104. 'selectedLocation' => $location ? [$location->getTitle() => $location->getId()] : [],
  105. 'selectedLocationId' => $location ? $location->getId() : null,
  106. ]);
  107. $discussionSearchForm = $form = $this->createForm(DiscussionSearchType::class, $discussion, [
  108. 'action' => $this->generateUrl('neighborhood_discussion_list'),
  109. 'method' => 'GET',
  110. 'flatLocations' => serialize($locationManager->getFlatByMaxLevel(2)),
  111. 'selectedLocation' => $location ? [$location->getTitle() => $location->getId()] : [],
  112. 'selectedLocationId' => $location ? $location->getId() : null,
  113. ]);
  114. $locationChildren = [];
  115. if ($location) {
  116. $locationChildren = $this->supplyDemandManager->getLocationChildren($location);
  117. }
  118. $criteria = [
  119. 'status' => DiscussionStatus::APPROVED,
  120. 'location' => $locationChildren,
  121. 'category' => $request->query->get('category'),
  122. 'tab' => $request->query->get('tab', DiscussionSearchTabs::TRENDING),
  123. 'sort' => 'commentsCounter',
  124. ];
  125. $discussions = $this->discussionRepository->filter($criteria);
  126. if (
  127. $request->query->get('submitted')
  128. && DiscussionSearchTabs::TRENDING == $request->query->get('tab')
  129. && empty($discussions)
  130. ) {
  131. $criteria = $request->query->all();
  132. $criteria['tab'] = DiscussionSearchTabs::ALLQUESTIONS;
  133. return $this->redirectToRoute('neighborhood_discussion_list', $criteria);
  134. }
  135. $discussions = $this->paginator->paginate(
  136. $discussions,
  137. $request->query->getInt('page', 1),
  138. 10
  139. );
  140. $latestLocationListings = [];
  141. if ($location) {
  142. $latestLocationListings = $this->listingRepository->getLatestListings(4, $this->supplyDemandManager->getLocationChildren($location))->getQuery()->getResult();
  143. }
  144. return $this->render('@AqarmapDiscussionBundle/Discussion/discussions.html.twig', [
  145. 'subLinksSections' => $this->sectionRepository->getSearchableSection(),
  146. 'discussionForm' => $discussionForm,
  147. 'responsiveDiscussionForm' => $discussionForm,
  148. 'discussionSearchForm' => $discussionSearchForm,
  149. 'discussions' => $discussions,
  150. 'interestsListings' => $interestsListings,
  151. 'quick_registration_form' => $this->createForm(QuickRegistrationFormType::class, null, [
  152. 'method' => 'POST',
  153. 'action' => $this->generateUrl('aqarmap_user_quick_registration'),
  154. ]),
  155. 'location' => $location,
  156. 'latestLocationListings' => $latestLocationListings,
  157. 'popularArticles' => json_decode(
  158. $this->wordpressManager->getCachedPosts($request->getLocale()),
  159. true
  160. ),
  161. 'aqarmap_advice_link' => $this->parameterBag->get('aqarmap_advice_url'),
  162. 'form' => $this->createQuickLeadForm(),
  163. 'metaDescriptionContent' => $this->discussionManager->concatTitles($discussions->getItems()),
  164. 'selectedCategory' => $request->get('category') ?
  165. $this->categoryRepository->find($request->get('category')) : null,
  166. ]);
  167. }
  168. /**
  169. * Creates a new Discussion entity.
  170. */
  171. #[Route(path: 'question/create/', name: 'neighborhood_discussion_create', methods: ['POST'])]
  172. public function create(
  173. Request $request,
  174. LocationManager $locationManager,
  175. SessionInterface $session,
  176. ): RedirectResponse {
  177. $entity = new Discussion();
  178. $form = $this->createForm(DiscussionType::class, $entity, [
  179. 'action' => $this->generateUrl('neighborhood_discussion_create'),
  180. 'method' => 'POST',
  181. 'flatLocations' => serialize($locationManager->getLocationsArrayWithParent($request->getLocale())['searchable']) ?? serialize([]),
  182. ]);
  183. $location = null;
  184. if ($locationId = $request->request->get('location')) {
  185. $location = $this->locationRepository->find($locationId);
  186. $request->request->set('location', null);
  187. $form->get('location')->setData(null);
  188. }
  189. $form->handleRequest($request);
  190. if ($form->isValid() && $this->getUser()) {
  191. $entity->setLocation($location);
  192. $entity->setUser($this->getUser());
  193. $this->em->persist($entity);
  194. $this->em->flush();
  195. $this->discussionManager->createSubscriber($this->getUser(), $entity);
  196. $session->getFlashBag()->add(
  197. 'success',
  198. $this->translator->trans('neighborhoods.discussion.success_message')
  199. );
  200. }
  201. return $this->redirect($request->headers->get('referer') ?: $this->generateUrl('homepage'));
  202. }
  203. /**
  204. * Lists all Discussion comments.
  205. */
  206. #[Route(path: 'ask-neighbors/{discussion}/answers/', name: 'neighborhood_discussion_comments', methods: ['GET'])]
  207. #[Route(path: 'discussion/{discussion}/comments/', name: 'discussion_comments', methods: ['GET'])]
  208. #[Route(path: 'question/{discussion}/comments/', name: 'neighborhood_question_discussion_comments', methods: ['GET'])]
  209. public function discussionComments(
  210. Request $request,
  211. DatabaseNotification $databaseNotification,
  212. LoggerInterface $logger,
  213. SessionInterface $session,
  214. ?Discussion $discussion = null,
  215. ) {
  216. if ($request->get('notification')) {
  217. try {
  218. $databaseNotification->markOneAsRead($request->get('notification'));
  219. } catch (\Exception $exception) {
  220. $logger->log(LogLevel::ERROR, $exception->getMessage());
  221. }
  222. }
  223. if (!$discussion) {
  224. return $this->redirectToRoute('neighborhood_discussion_list', [], Response::HTTP_MOVED_PERMANENTLY);
  225. }
  226. if ($discussion->getDeletedAt()) {
  227. if ($discussion->getUser() != $this->getUser()) {
  228. return $this->redirectWhenDiscussionDeleted($discussion);
  229. }
  230. $session->getFlashBag()->add(
  231. 'warning',
  232. $this->translator->trans('neighborhoods.discussion.already_deleted')
  233. );
  234. } elseif (DiscussionStatus::PENDING == $discussion->getStatus()) {
  235. $session->getFlashBag()->add(
  236. 'warning',
  237. $this->translator->trans('neighborhoods.discussion.pending_approval')
  238. );
  239. }
  240. if (\in_array($request->get('_route'), [
  241. 'neighborhood_question_discussion_comments',
  242. 'discussion_comments',
  243. ])) {
  244. return $this->redirectToRoute('neighborhood_discussion_comments', array_merge(['discussion' => $discussion->getId()], $request->query->all()), Response::HTTP_MOVED_PERMANENTLY);
  245. }
  246. $comment = new Comment();
  247. $commentForm = $this->createForm(CommentType::class, $comment, [
  248. 'action' => $this->generateUrl('neighborhood_comment_create', ['discussion' => $discussion->getId()]),
  249. 'method' => 'POST',
  250. ]);
  251. $user = $this->getUser();
  252. $userInterests = [];
  253. if ($user instanceof User) {
  254. $userInterests = $this->userInterestRepository->getUserInterests($user, 4)->getQuery()->getResult();
  255. }
  256. $userInterests[] = $this->userInterestManager->getListingsMayBeInterested($discussion->getLocation());
  257. $interestsListings = $this->discussionManager->getInterestsListings($userInterests);
  258. return $this->render('@AqarmapDiscussionBundle/Discussion/discussionComments.html.twig', [
  259. 'commentForm' => $commentForm,
  260. 'discussion' => $discussion,
  261. 'discussionComments' => $this->commentRepository->getCommentsWithLikes($discussion),
  262. 'interestsListings' => $interestsListings,
  263. 'quick_registration_form' => $this->createForm(QuickRegistrationFormType::class, null, [
  264. 'method' => 'POST',
  265. 'action' => $this->generateUrl('aqarmap_user_quick_registration'),
  266. ]),
  267. 'popularArticles' => json_decode(
  268. $this->wordpressManager->getCachedPosts($request->getLocale()),
  269. true
  270. ),
  271. 'aqarmap_advice_link' => $this->parameterBag->get('aqarmap_advice_url'),
  272. ]);
  273. }
  274. /**
  275. * Add Discussion Subscribers.
  276. */
  277. #[Route(path: 'question/add/subscriber/{discussion}/', name: 'neighborhood_discussion_add_subscriber')]
  278. public function addDiscussionSubscriber(
  279. Discussion $discussion,
  280. Request $request,
  281. SessionInterface $session,
  282. ): RedirectResponse {
  283. $user = $this->getUser();
  284. $this->discussionManager->createSubscriber($user, $discussion);
  285. $session->getFlashBag()->add(
  286. 'success',
  287. $this->translator->trans('neighborhoods.discussion.subscriber_success_message')
  288. );
  289. return $this->redirect($request->headers->get('referer') ?: $this->generateUrl('homepage'));
  290. }
  291. /**
  292. * Remove Discussion Subscribers.
  293. */
  294. #[Route(path: 'question/remove/subscriber/{discussion}/', name: 'neighborhood_discussion_remove_subscriber')]
  295. public function unsetDiscussionSubscriber(
  296. Discussion $discussion,
  297. Request $request,
  298. SessionInterface $session,
  299. ): RedirectResponse {
  300. $this->discussionManager->unsetSubscriber($this->getUser(), $discussion);
  301. $session->getFlashBag()->add(
  302. 'success',
  303. $this->container->get('translator')->trans('neighborhoods.discussion.subscriber_unset_message')
  304. );
  305. return $this->redirect($request->headers->get('referer') ?: $this->generateUrl('homepage'));
  306. }
  307. /**
  308. * @return RedirectResponse
  309. *
  310. * @throws BadRequestHttpException
  311. */
  312. #[Route(path: 'question/un-subscribe/{user}/{discussion}/', name: 'neighborhood_discussion_mail_remove_subscriber')]
  313. public function unsubscribeConfirm(
  314. User $user,
  315. Discussion $discussion,
  316. Request $request,
  317. SessionInterface $session,
  318. ) {
  319. /* Valid User Md5 valid */
  320. if (md5($user->getId()).$this->parameterBag->get('secret') !== $request->query->get('token')) {
  321. throw new BadRequestHttpException('Permission Denied !');
  322. }
  323. // Create confirmation form (button)
  324. $form = $this->createForm(ConfirmFormType::class, null, [
  325. 'method' => 'POST',
  326. 'action' => $this->generateUrl(
  327. 'neighborhood_discussion_mail_remove_subscriber',
  328. ['discussion' => $discussion->getId(), 'user' => $user->getId(), 'token' => $request->query->get('token')]
  329. ),
  330. ]);
  331. $form->handleRequest($request);
  332. if ($form->isSubmitted() && $form->isValid()) {
  333. if ($form->get('confirm')->isClicked()) {
  334. $this->discussionManager->unsetSubscriber($user, $discussion);
  335. $session->getFlashBag()->add(
  336. 'success',
  337. $this->translator->trans('neighborhoods.discussion.subscriber_unset_message')
  338. );
  339. return $this->redirectToRoute('homepage');
  340. }
  341. }
  342. return $this->render('@AqarmapDiscussionBundle/Discussion/unsubscribeConfirm.html.twig', [
  343. 'form' => $form,
  344. 'discussion' => $discussion,
  345. ]);
  346. }
  347. /**
  348. * Edit Comment entity.
  349. *
  350. * @return array|RedirectResponse
  351. */
  352. #[Route(path: 'question/{discussion}/edit', name: 'neighborhood_discussion_edit')]
  353. public function edit(
  354. Request $request,
  355. Discussion $discussion,
  356. LocationManager $locationManager,
  357. SessionInterface $session,
  358. ) {
  359. $form = $this->createForm(DiscussionType::class, $discussion, [
  360. 'action' => $this->generateUrl('neighborhood_discussion_edit', [
  361. 'discussion' => $discussion->getId(),
  362. ]),
  363. 'method' => 'POST',
  364. 'flatLocations' => serialize($locationManager->getLocationsArrayWithParent($request->getLocale())['searchable']) ?? serialize([]),
  365. ]);
  366. $form->handleRequest($request);
  367. if ($form->isSubmitted() && $form->isValid()) {
  368. $em = $this->managerRegistry->getManager();
  369. $discussion->setStatus(DiscussionStatus::PENDING);
  370. $em->persist($discussion);
  371. $em->flush();
  372. $session->getFlashBag()->add(
  373. 'success',
  374. $this->translator->trans('neighborhoods.comment.edit_success_message')
  375. );
  376. return $this->redirectToRoute('neighborhood_discussion_comments', ['discussion' => $discussion->getId()]);
  377. }
  378. return $this->render('@AqarmapDiscussionBundle/Discussion/edit.html.twig', [
  379. 'form' => $form,
  380. 'discussion' => $discussion,
  381. ]);
  382. }
  383. /**
  384. * Delete Discussion.
  385. *
  386. * @return RedirectResponse|Response
  387. */
  388. #[Route(path: 'question/{discussion}/delete', name: 'discussion_delete')]
  389. public function delete(Discussion $discussion, SessionInterface $session): RedirectResponse
  390. {
  391. if (!$discussion) {
  392. throw $this->createNotFoundException('The Discussion does not exist');
  393. }
  394. $this->discussionManager->remove($discussion);
  395. $session->getFlashBag()->add(
  396. 'success',
  397. $this->translator->trans('neighborhoods.discussion.delete_success_message')
  398. );
  399. return $this->redirectToRoute('neighborhood_discussion_list');
  400. }
  401. /**
  402. * Redirect when the discssuion is deleted.
  403. *
  404. * @return RedirectResponse
  405. */
  406. public function redirectWhenDiscussionDeleted(?Discussion $discussion = null)
  407. {
  408. $criteria = [];
  409. if ($discussion) {
  410. $location = $discussion->getLocation();
  411. if ($location) {
  412. $criteria['location'] = $location->getId();
  413. }
  414. $category = $discussion->getCategory();
  415. if ($category) {
  416. $criteria['category'] = $category->getId();
  417. }
  418. }
  419. return $this->redirectToRoute('neighborhood_discussion_list', $criteria, Response::HTTP_MOVED_PERMANENTLY);
  420. }
  421. /**
  422. * Create Quick Lead Form.
  423. */
  424. private function createQuickLeadForm(): FormInterface
  425. {
  426. return $this->createForm(
  427. QuickLeadType::class,
  428. null,
  429. [
  430. 'action' => $this->generateUrl('add_quick_lead'),
  431. 'method' => 'POST',
  432. ]
  433. );
  434. }
  435. #[Route(path: 'unsubscribe-ask-neighbors/{token}/{location}', requirements: ['id' => '\d+'], name: 'aqarmap_discussion_unsubscribe', methods: ['GET'])]
  436. public function unsubscribe(Request $request, SessionInterface $session, ?Location $location = null): RedirectResponse
  437. {
  438. $user = $this->userManager->findEncryptedBy($request->get('token'));
  439. if (!$user) {
  440. throw new \Exception('User Not Found!');
  441. }
  442. $criteria = [
  443. 'user' => $user,
  444. ];
  445. if ($location) {
  446. $criteria['location'] = $location;
  447. }
  448. $this->userInterestManager->removeBy($criteria);
  449. $session->getFlashBag()->add(
  450. 'success',
  451. $this->translator
  452. ->trans($location ? 'neighborhoods.unsubscribe_from_successeded' : 'neighborhoods.unsubscribe_all_successeded', ['%location%' => $location])
  453. );
  454. return $this->redirectToRoute('homepage');
  455. }
  456. /**
  457. * Remove the page query if equals one.
  458. *
  459. * @return RedirectResponse|void
  460. */
  461. private function removePageQuery(Request $request)
  462. {
  463. if ($request->query->getInt('page') && 1 == $request->query->getInt('page')) {
  464. $request->query->remove('page');
  465. return $this->redirectToRoute($request->get('_route'), $request->query->all(), Response::HTTP_MOVED_PERMANENTLY);
  466. }
  467. }
  468. }