src/Controller/FeedController.php line 734

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\DTO\HelpfulCommentDto;
  4. use App\DTO\LikeDto;
  5. use App\DTO\SaveCommentDto;
  6. use App\DTO\SavePostDto;
  7. use App\Entity\Image;
  8. use App\Entity\Post;
  9. use App\Entity\Token;
  10. use App\Entity\User;
  11. use App\Helper\AppversionHelper;
  12. use App\Helper\SettingConstants;
  13. use App\Response\PostResponse;
  14. use App\Service\CommunityNoteService;
  15. use App\Service\ContentService;
  16. use App\Service\FeedService;
  17. use App\Service\DynamicLinksService;
  18. use App\Service\FreshdeskService;
  19. use App\Service\GardenService;
  20. use App\Service\ImageService;
  21. use App\Service\MailService;
  22. use App\Service\NotificationService;
  23. use App\Service\PushNotificationService;
  24. use App\Service\SlackNotificationService;
  25. use App\Service\TagConnectionService;
  26. use App\Service\UserService;
  27. use App\Structures\PushNotification;
  28. use Nelmio\ApiDocBundle\Annotation\Model;
  29. use OpenApi\Annotations as OA;
  30. use Symfony\Component\Cache\Adapter\ApcuAdapter;
  31. use Symfony\Component\HttpFoundation\BinaryFileResponse;
  32. use Symfony\Component\HttpFoundation\JsonResponse;
  33. use Symfony\Component\HttpFoundation\Request;
  34. use Symfony\Component\HttpFoundation\Response;
  35. use Symfony\Contracts\Cache\ItemInterface;
  36. /**
  37.  * @OA\Tag(name="Community")
  38.  */
  39. class FeedController extends BaseController
  40. {
  41.     private FeedService $feedService;
  42.     private UserService $userService;
  43.     private PushNotificationService $pushNotificationService;
  44.     private ImageService $imageService;
  45.     private NotificationService $notificationService;
  46.     private FreshdeskService $freshdeskService;
  47.     private MailService $mailService;
  48.     private CommunityNoteService $communityNoteService;
  49.     private DynamicLinksService $dynamicLinksService;
  50.     private TagConnectionService $tagConnectionService;
  51.     private GardenService $gardenService;
  52.     private SlackNotificationService $slackNotificationService;
  53.     private ContentService $contentService;
  54.     public function __construct(
  55.         FeedService $feedService,
  56.         UserService $userService,
  57.         PushNotificationService $pushNotificationService,
  58.         ImageService $imageService,
  59.         NotificationService $notificationService,
  60.         FreshdeskService $freshdeskService,
  61.         MailService $mailService,
  62.         CommunityNoteService $communityNoteService,
  63.         DynamicLinksService $dynamicLinksService,
  64.         TagConnectionService $tagConnectionService,
  65.         GardenService $gardenService,
  66.         SlackNotificationService $slackNotificationService,
  67.         ContentService $contentService
  68.     ) {
  69.         $this->feedService $feedService;
  70.         $this->userService $userService;
  71.         $this->pushNotificationService $pushNotificationService;
  72.         $this->imageService $imageService;
  73.         $this->notificationService $notificationService;
  74.         $this->freshdeskService $freshdeskService;
  75.         $this->mailService $mailService;
  76.         $this->communityNoteService $communityNoteService;
  77.         $this->dynamicLinksService $dynamicLinksService;
  78.         $this->tagConnectionService $tagConnectionService;
  79.         $this->gardenService $gardenService;
  80.         $this->slackNotificationService $slackNotificationService;
  81.         $this->contentService $contentService;
  82.     }
  83.     /**
  84.      * @OA\Get(
  85.      *     description="Lists a feed of all posts for the given user",
  86.      *     @OA\Response(
  87.      *         response=200,
  88.      *         description="Success",
  89.      *         @OA\JsonContent(
  90.      *             type="object",
  91.      *             @OA\Property(property="posts", type="array",
  92.      *                  @OA\Items(ref=@Model(type=PostResponse::class))
  93.      *              ),
  94.      *              @OA\Property(property="_metadata",type="object",
  95.      *                  @OA\Property(property="currentPage", type="number"),
  96.      *                  @OA\Property(property="totalPages", type="number"),
  97.      *                  @OA\Property(property="unreadNotifications", type="number"),
  98.      *                  @OA\Property(property="trendingTags", type="array", @OA\Items(type="string")),
  99.      *                  @OA\Property(property="myTags", type="array", @OA\Items(type="string")),
  100.      *              )
  101.      *          )
  102.      *     ),
  103.      * )
  104.      */
  105.     public function listAction(Request $request): JsonResponse
  106.     {
  107.         $user $this->getUser();
  108.         $page $request->get('page');
  109.         $query $request->get('q');
  110.         $category $request->get('c');
  111.         $userFilter $request->get('u');
  112.         if (empty($user)) {
  113.             return new JsonResponse('forbidden'403);
  114.         }
  115.         /** @var Token $token */
  116.         $token $user->getToken()->first();
  117.         $accessToken $token->getToken();
  118.         if (empty($page)) {
  119.             $page 1;
  120.         }
  121.         $isV2 $this->isApiV2($request);
  122.         $filters $this->createFilters($query$category$userFilter);
  123.         // no app-version set means we have an older app which does not support urls in richtext
  124.         $appVersion $request->headers->get('app-version');
  125.         if (empty($appVersion)) {
  126.             $this->feedService->setFlag(FeedService::FLAG_NO_URLS_IN_RICHTEXT);
  127.         } elseif (AppversionHelper::hasRequiredVersion($appVersion'2.2.4')) {
  128.             $this->feedService->setFlag(FeedService::FLAG_LIST_ARTICLES);
  129.             $this->feedService->setFlag(FeedService::FLAG_REDUCE_DATA_IN_PROFILE);
  130.         }
  131.         $feed $this->feedService->fetchFeed($user$accessToken$isV2$filters$page);
  132.         $metadata = [
  133.             'currentPage' => $feed['currentPage'],
  134.             'totalPages' => $feed['totalPages'],
  135.             'unreadNotifications' => $this->notificationService->getUnreadNotificationCount($user),
  136.             'trendingTags' => $this->feedService->fetchTrendingTags(),
  137.             'myTags' => [], // todo: add if users can save tags
  138.         ];
  139.         $stickyNote $this->communityNoteService->getCurrentNote($user->getLocale() ?? User::DEFAULT_LOCALE);
  140.         if ($stickyNote !== null) {
  141.             $metadata['stickyNote'] = $stickyNote;
  142.         }
  143.         $feed['_metadata'] = $metadata;
  144.         return new JsonResponse($feed);
  145.     }
  146.     public function userHasAnswersAction(): JsonResponse
  147.     {
  148.         $user $this->getUser();
  149.         if (empty($user)) {
  150.             return new JsonResponse('forbidden'403);
  151.         }
  152.         $hasAnswers $this->feedService->userHasAnswerToPost($user);
  153.         return new JsonResponse(['hasAnswers' => $hasAnswers]);
  154.     }
  155.     /**
  156.      * @OA\Get(
  157.      *     description="Lists a feed of customized posts for the given user",
  158.      *     @OA\Response(
  159.      *         response=200,
  160.      *         description="Success",
  161.      *         @OA\JsonContent(
  162.      *             type="object",
  163.      *             @OA\Property(property="posts", type="array",
  164.      *                  @OA\Items(ref=@Model(type=PostResponse::class))
  165.      *              ),
  166.      *              @OA\Property(property="_metadata",type="object",
  167.      *                  @OA\Property(property="currentPage", type="number"),
  168.      *                  @OA\Property(property="totalPages", type="number"),
  169.      *                  @OA\Property(property="unreadNotifications", type="number"),
  170.      *                  @OA\Property(property="trendingTags", type="array", @OA\Items(type="string")),
  171.      *                  @OA\Property(property="myTags", type="array", @OA\Items(type="string")),
  172.      *              )
  173.      *          )
  174.      *     ),
  175.      * )
  176.      */
  177.     public function listCustomizedAction(Request $request): JsonResponse
  178.     {
  179.         $user $this->getUser();
  180.         $page $request->get('page') ?? 1;
  181.         $query $request->get('q');
  182.         $category $request->get('c');
  183.         $userFilter $request->get('u');
  184.         if (empty($user)) {
  185.             return new JsonResponse('forbidden'403);
  186.         }
  187.         $this->feedService->setFlag(FeedService::FLAG_LIST_ARTICLES);
  188.         $this->feedService->setFlag(FeedService::FLAG_REDUCE_DATA_IN_PROFILE);
  189.         $filters $this->createFilters($query$category$userFilter);
  190.         $feed $this->feedService->fetchCustomizedFeed($user$filters$page);
  191.         $metadata = [
  192.             'currentPage' => $feed['currentPage'],
  193.             'totalPages' => $feed['totalPages'],
  194.             'unreadNotifications' => $this->notificationService->getUnreadNotificationCount($user),
  195.             'trendingTags' => $this->feedService->fetchTrendingTags(),
  196.             'myTags' => $this->tagConnectionService->listConnections($user),
  197.         ];
  198.         $stickyNote $this->communityNoteService->getCurrentNote($user->getLocale() ?? User::DEFAULT_LOCALE);
  199.         if ($stickyNote !== null) {
  200.             $metadata['stickyNote'] = $stickyNote;
  201.         }
  202.         $feed['_metadata'] = $metadata;
  203.         return new JsonResponse($feed);
  204.     }
  205.     public function listBookmarksAction(Request $request): JsonResponse
  206.     {
  207.         $user $this->getUser();
  208.         if (empty($user)) {
  209.             return new JsonResponse('forbidden'403);
  210.         }
  211.         /** @var Token $token */
  212.         $token $user->getToken()->first();
  213.         $accessToken $token->getToken();
  214.         $isV2 $this->isApiV2($request);
  215.         $feed $this->feedService->fetchBookmarks($user$accessToken$isV2);
  216.         return new JsonResponse($feed);
  217.     }
  218.     /**
  219.      * @OA\Post(
  220.      *     description="Creates a new post in the community",
  221.      *     @OA\Response(
  222.      *         response=200,
  223.      *         description="Post sucessfully created",
  224.      *         @OA\JsonContent(
  225.      *             type="object",
  226.      *             @OA\Property(property="posts", type="array",
  227.      *                  @OA\Items(ref=@Model(type=PostResponse::class))
  228.      *              )
  229.      *          )
  230.      *     ),
  231.      *     @OA\Parameter(
  232.      *         name="body",
  233.      *         in="path",
  234.      *         required=true,
  235.      *         @OA\JsonContent(
  236.      *             type="object",
  237.      *             @OA\Property(property="text", type="string"),
  238.      *             @OA\Property(property="image", type="string"),
  239.      *             @OA\Property(property="images", type="array",
  240.      *                 @OA\Items(
  241.      *                     @OA\Property(property="base64")
  242.      *                 )
  243.      *             ),
  244.      *             @OA\Property(property="isSupportRequest", type="boolean"),
  245.      *             @OA\Property(property="isContentPiece", type="boolean"),
  246.      *             @OA\Property(property="browser", type="string"),
  247.      *             @OA\Property(property="version", type="string"),
  248.      *             @OA\Property(property="appVersion", type="string"),
  249.      *             @OA\Property(property="appOS", type="string"),
  250.      *             @OA\Property(property="appDevice", type="string"),
  251.      *         ),
  252.      *     )
  253.      *  )
  254.      * @param Request $request
  255.      * @return JsonResponse
  256.      */
  257.     public function savePostAction(SavePostDto $dtoRequest $request): JsonResponse
  258.     {
  259.         $user $this->getUser();
  260.         if (empty($user)) {
  261.             return new JsonResponse('forbidden'403);
  262.         }
  263.         /** @var Token $token */
  264.         $token $user->getToken()->first();
  265.         $accessToken $token->getToken();
  266.         // check if post is support request
  267.         $userEmailValid str_contains($user->getEmail(), '@');
  268.         if ($dto->isSupportQuestion === true && $userEmailValid) {
  269.             $additionalData = [
  270.                 'browser' => $dto->browser,
  271.                 'version' => $dto->version,
  272.                 'appVersion' => $dto->appVersion,
  273.                 'appOS' => $dto->appOS,
  274.                 'appDevice' => $dto->appDevice,
  275.             ];
  276.             $this->freshdeskService->createSupportTicket($user$dto->text$dto->image$additionalData);
  277.             return new JsonResponse(['post' => []]);
  278.         } else {
  279.             if ($dto->isContentPiece === true) {
  280.                 $this->mailService->sendAppcontentMail($user$dto->text$dto->images);
  281.                 return new JsonResponse(['post' => []]);
  282.             } else {
  283.                 if ($dto->gardenId) {
  284.                     $gardenHash $this->gardenService->shareGarden($dto->user$dto->gardenId);
  285.                     if ($gardenHash === false) {
  286.                         return new JsonResponse('cannot share non existing garden'400);
  287.                     }
  288.                     $dto->gardenHash $gardenHash;
  289.                 }
  290.                 $blocked $this->feedService->checkBlocklist($dto->text);
  291.                 if ($blocked) {
  292.                     $this->slackNotificationService->sendMessageToChannel(SlackNotificationService::CHANNEL_C3PO,
  293.                         ':poop: Post blocked due to blocklist:' "\n" .
  294.                         'Posted by: ' $user->getDisplayName() . '(id: ' $user->getId() . ')' "\n" .
  295.                         'Text: ' $dto->text);
  296.                     return new JsonResponse('content blocked'400);
  297.                 }
  298.                 $postEntity $this->feedService->savePost($dto);
  299.                 $isV2 $this->isApiV2($request);
  300.                 $deeplink $this->dynamicLinksService->createCommunityPostLink($postEntity->getId());
  301.                 $this->feedService->addDeeplinkToPost($postEntity$deeplink);
  302.                 $post $this->feedService->fetchPost($user$postEntity->getId(), $accessTokentrue$isV2);
  303.                 $notificationsToPush $this->notificationService->createNotificationsForPost($postEntity);
  304.                 if (count($notificationsToPush) > 0) {
  305.                     $this->pushNotificationService->createPushNotifications($notificationsToPush);
  306.                 }
  307.                 return new JsonResponse(['post' => $post]);
  308.             }
  309.         }
  310.     }
  311.     public function editPostAction(Request $request)
  312.     {
  313.         $user $this->getUser();
  314.         $data json_decode($request->getContent(), true);
  315.         $postId $request->get('postId');
  316.         if (empty($user)) {
  317.             return new JsonResponse('forbidden'403);
  318.         }
  319.         /** @var Token $token */
  320.         $token $user->getToken()->first();
  321.         $accessToken $token->getToken();
  322.         $post $this->feedService->updatePost($user$postId$data);
  323.         if ($post === false) {
  324.             return new JsonResponse('bad request'400);
  325.         }
  326.         $isV2 $this->isApiV2($request);
  327.         $post $this->feedService->fetchPost($user$post->getId(), $accessTokentrue$isV2);
  328.         return new JsonResponse(['post' => $post]);
  329.     }
  330.     public function deletePostAction(Request $request)
  331.     {
  332.         $user $this->getUser();
  333.         $postId $request->get('postId');
  334.         if (empty($user)) {
  335.             return new JsonResponse('forbidden'403);
  336.         }
  337.         $post $this->feedService->fetchPost($user$postId);
  338.         if ($post === null) {
  339.             return new JsonResponse('bad request'400);
  340.         }
  341.         if (!$this->feedService->postBelongsToUser($user$postId)) {
  342.             return new JsonResponse('bad request'400);
  343.         }
  344.         $this->notificationService->deleteNotificationsForPost($postId);
  345.         $result $this->feedService->deletePost($user$postId);
  346.         if ($result === false) {
  347.             return new JsonResponse('bad request'400);
  348.         }
  349.         return new JsonResponse(['success' => true]);
  350.     }
  351.     /**
  352.      * @OA\Post(
  353.      *     description="Creates a new comment for a given post",
  354.      *     @OA\Response(
  355.      *         response=200,
  356.      *         description="Comment sucessfully created",
  357.      *         @OA\JsonContent(
  358.      *             type="object",
  359.      *             @OA\Property(property="post", type="object", ref=@Model(type=PostResponse::class))
  360.      *          )
  361.      *     ),
  362.      *     @OA\Parameter(
  363.      *         name="body",
  364.      *         in="path",
  365.      *         required=true,
  366.      *         @OA\JsonContent(
  367.      *             type="object",
  368.      *             @OA\Property(property="parentPostId", type="int"),
  369.      *             @OA\Property(property="text", type="string"),
  370.      *             @OA\Property(property="image", type="string"),
  371.      *         ),
  372.      *     ),
  373.      *  )
  374.      * @param Request $request
  375.      * @return JsonResponse
  376.      */
  377.     public function saveCommentAction(SaveCommentDto $dtoRequest $request): JsonResponse
  378.     {
  379.         /** @var User $user */
  380.         $user $this->getUser();
  381.         if (empty($user)) {
  382.             return new JsonResponse('forbidden'403);
  383.         }
  384.         /** @var Token $token */
  385.         $token $user->getToken()->first();
  386.         $accessToken $token->getToken();
  387.         $comment $this->feedService->saveComment($dto);
  388.         /** @var Post $post */
  389.         $postEntity $this->feedService->fetchPost($user$dto->postIdnullfalse);
  390.         $notificationsToPush $this->notificationService->createNotificationsForComment($comment);
  391.         if (count($notificationsToPush) > 0) {
  392.             $this->pushNotificationService->createPushNotifications($notificationsToPush);
  393.         }
  394.         if ($postEntity->getUser()->getId() !== $user->getId()) {
  395.             // send push notification to author
  396.             $pn $this->pushNotificationService->createPushNotification(
  397.                 $postEntity->getUser(),
  398.                 NotificationService::NOTIFICATION_TYPE_ANSWER_TO_YOUR_POST,
  399.                 ['%name%' => $user->getDisplayName()]);
  400.             $pn->setView(PushNotification::VIEW_COMMUNITY_STACK);
  401.             $pn->setViewParams(['screen' => PushNotification::SCREEN_NOTIFICATIONS]);
  402.             $pn->setWithBadgeCount(true);
  403.             $this->pushNotificationService->sendPushNotification($postEntity->getUser(), $pn);
  404.         }
  405.         $isV2 $this->isApiV2($request);
  406.         $post $this->feedService->fetchPost($user$dto->postId$accessTokentrue$isV2);
  407.         return new JsonResponse(['post' => $post]);
  408.     }
  409.     public function editCommentAction(Request $request): JsonResponse
  410.     {
  411.         $user $this->getUser();
  412.         $postId $request->get('postId');
  413.         $commentId $request->get('commentId');
  414.         $data json_decode($request->getContent(), true);
  415.         if (empty($user)) {
  416.             return new JsonResponse('forbidden'403);
  417.         }
  418.         /** @var Token $token */
  419.         $token $user->getToken()->first();
  420.         $accessToken $token->getToken();
  421.         $this->feedService->updateComment($user$commentId$data);
  422.         $isV2 $this->isApiV2($request);
  423.         $post $this->feedService->fetchPost($user$postId$accessTokentrue$isV2);
  424.         return new JsonResponse(['post' => $post]);
  425.     }
  426.     public function deleteCommentAction(Request $request): JsonResponse
  427.     {
  428.         $user $this->getUser();
  429.         $postId $request->get('postId');
  430.         $commentId $request->get('commentId');
  431.         if (empty($user)) {
  432.             return new JsonResponse('forbidden'403);
  433.         }
  434.         if (!$this->feedService->commentBelongsToUser($user$commentId)) {
  435.             return new JsonResponse('bad request'400);
  436.         }
  437.         $this->notificationService->deleteNotificationsForComment($commentId);
  438.         $result $this->feedService->deleteComment($user$commentId);
  439.         if ($result === false) {
  440.             return new JsonResponse('bad request'400);
  441.         }
  442.         /** @var Token $token */
  443.         $token $user->getToken()->first();
  444.         $accessToken $token->getToken();
  445.         $isV2 $this->isApiV2($request);
  446.         $post $this->feedService->fetchPost($user$postId$accessTokentrue$isV2);
  447.         return new JsonResponse(['post' => $post]);
  448.     }
  449.     public function bookmarkAction(Request $request): JsonResponse
  450.     {
  451.         $postId $request->get('postId');
  452.         $user $this->getUser();
  453.         if (empty($user)) {
  454.             return new JsonResponse('forbidden'403);
  455.         }
  456.         $this->feedService->bookmarkPost($user$postId);
  457.         $post $this->getPost($user$postId$request);
  458.         return new JsonResponse(['post' => $post]);
  459.     }
  460.     public function deleteBookmarkAction(Request $request): JsonResponse
  461.     {
  462.         $postId $request->get('postId');
  463.         $user $this->getUser();
  464.         if (empty($user)) {
  465.             return new JsonResponse('forbidden'403);
  466.         }
  467.         $this->feedService->deleteBookmark($user$postId);
  468.         $post $this->getPost($user$postId$request);
  469.         return new JsonResponse(['post' => $post]);
  470.     }
  471.     public function imageAction(Request $request): BinaryFileResponse|JsonResponse
  472.     {
  473.         $hash $request->get('hash');
  474.         $filePath $this->feedService->getImageFilename($hash);
  475.         if ($filePath !== false) {
  476.             $response = new BinaryFileResponse($filePath200);
  477.         } else {
  478.             return new JsonResponse(['error' => 'no_image'], 404);
  479.         }
  480.         return $response;
  481.     }
  482.     public function likeCommentAction(LikeDto $dtoRequest $request): JsonResponse
  483.     {
  484.         $user $this->getUser();
  485.         if (empty($user)) {
  486.             return new JsonResponse('forbidden'403);
  487.         }
  488.         $like $this->feedService->likeComment($user$dto->commentId$dto->emotion);
  489.         if ($like !== false) {
  490.             $this->notificationService->createNotificationForCommentLike($like);
  491.             $author $like->getComment()->getUser();
  492.             // send push notification to author
  493.             $pn $this->pushNotificationService->createPushNotification(
  494.                 $author,
  495.                 NotificationService::NOTIFICATION_TYPE_LIKED_YOUR_COMMENT,
  496.                 ['%name%' => $user->getDisplayName()]
  497.             );
  498.             $pn->setView(PushNotification::VIEW_COMMUNITY_STACK);
  499.             $pn->setViewParams(['screen' => PushNotification::SCREEN_NOTIFICATIONS]);
  500.             $pn->setWithBadgeCount(true);
  501.             $this->pushNotificationService->sendPushNotification($author$pn);
  502.         }
  503.         $post $this->getPost($user$dto->postId$request);
  504.         return new JsonResponse(['post' => $post]);
  505.     }
  506.     public function dislikeCommentAction(int $postIdint $commentIdRequest $request): JsonResponse
  507.     {
  508.         $user $this->getUser();
  509.         if (empty($user)) {
  510.             return new JsonResponse('forbidden'403);
  511.         }
  512.         $this->feedService->dislikeComment($user$commentId);
  513.         $post $this->getPost($user$postId$request);
  514.         return new JsonResponse(['post' => $post]);
  515.     }
  516.     public function likePostAction(LikeDto $dtoRequest $request): JsonResponse
  517.     {
  518.         $user $this->getUser();
  519.         if (empty($user)) {
  520.             return new JsonResponse('forbidden'403);
  521.         }
  522.         $like $this->feedService->likePost($user$dto->postId$dto->emotion);
  523.         if ($like !== false) {
  524.             $this->notificationService->createNotificationForLike($like);
  525.         }
  526.         $post $this->feedService->fetchPost($user$dto->postIdnullfalse);
  527.         if ($post->getUser()->getId() !== $user->getId()) {
  528.             $pn $this->pushNotificationService->createPushNotification(
  529.                 $post->getUser(),
  530.                 NotificationService::NOTIFICATION_TYPE_REACTED_TO_YOUR_POST,
  531.                 ['%name%' => $user->getDisplayName()]
  532.             );
  533.             $pn->setView(PushNotification::VIEW_COMMUNITY_STACK);
  534.             $pn->setViewParams(['screen' => PushNotification::SCREEN_NOTIFICATIONS]);
  535.             $pn->setWithBadgeCount(true);
  536.             $this->pushNotificationService->sendPushNotification($post->getUser(), $pn);
  537.         }
  538.         $post $this->getPost($user$dto->postId$request);
  539.         return new JsonResponse(['post' => $post]);
  540.     }
  541.     public function dislikePostAction(int $postIdRequest $request): JsonResponse
  542.     {
  543.         $user $this->getUser();
  544.         if (empty($user)) {
  545.             return new JsonResponse('forbidden'403);
  546.         }
  547.         $this->feedService->dislikePost($user$postId);
  548.         $post $this->getPost($user$postId$request);
  549.         return new JsonResponse(['post' => $post]);
  550.     }
  551.     public function profileImageAction(Request $request): BinaryFileResponse
  552.     {
  553.         $hash $request->get('hash');
  554.         $user $this->getUser();
  555.         $filePath $this->userService->getProfileImage($hash);
  556.         $thumbPath str_replace('.jpg''_thumb.jpg'$filePath);
  557.         if ($filePath === false || !file_exists($filePath)) {
  558.             // return random default user image
  559.             $number rand(14);
  560.             $filePath __DIR__ '/../../data/placeholder_images/user_' $number '.png';
  561.         } else {
  562.             if (!file_exists($thumbPath)) {
  563.                 $this->imageService->makeThumbnails($filePath$thumbPath200200);
  564.                 $filePath $thumbPath;
  565.             } else {
  566.                 $filePath $thumbPath;
  567.             }
  568.         }
  569.         return new BinaryFileResponse($filePath200);
  570.     }
  571.     public function reportPostAction(Request $request): JsonResponse
  572.     {
  573.         $user $this->getUser();
  574.         if (empty($user)) {
  575.             return new JsonResponse('forbidden'403);
  576.         }
  577.         $postId $request->get('postId');
  578.         $body json_decode($request->getContent(), true);
  579.         $post $this->feedService->fetchPost($user$postIdnullfalse);
  580.         $postHidden $this->feedService->reportPost($user$postId$body['reason']);
  581.         if ($postHidden && $post !== null) {
  582.             $this->slackNotificationService->sendMessageToChannel(SlackNotificationService::CHANNEL_C3PO,
  583.                 ':poop: Post hidden due to 3 or more reports:' "\n" .
  584.                 'Posted by: ' $post->getUser()->getDisplayName() . '(id: ' $post->getUser()->getId() . ')' "\n" .
  585.                 'Text: ' $post->getText());
  586.         }
  587.         if ($post !== null) {
  588.             $images '';
  589.             foreach ($post->getImageUrls() as $imageUrl) {
  590.                 $images .= $imageUrl " ";
  591.             }
  592.             $this->freshdeskService->createReportTicket($user, [
  593.                 'reason' => $body['reason'],
  594.                 'postId' => $post->getId(),
  595.                 'postText' => $post->getText(),
  596.                 'postLink' => $post->getDynamicLink(),
  597.                 'postImages' => $images
  598.             ]);
  599.         }
  600.         return new JsonResponse(null200);
  601.     }
  602.     public function reportCommentAction(Request $request): JsonResponse
  603.     {
  604.         $user $this->getUser();
  605.         if (empty($user)) {
  606.             return new JsonResponse('forbidden'403);
  607.         }
  608.         $postId $request->get('postId');
  609.         $commentId $request->get('commentId');
  610.         $body json_decode($request->getContent(), true);
  611.         $post $this->feedService->fetchPost($user$postIdnullfalse);
  612.         $comment null;
  613.         if ($post !== null) {
  614.             foreach ($post->getPostComment() as $comment) {
  615.                 if ($comment->getId() === $commentId) {
  616.                     break;
  617.                 }
  618.             }
  619.             if ($comment !== null) {
  620.                 $commentHidden $this->feedService->reportComment($user$commentId$body['reason']);
  621.                 if ($commentHidden && $comment !== null) {
  622.                     $this->slackNotificationService->sendMessageToChannel(SlackNotificationService::CHANNEL_C3PO,
  623.                         ':poop: Comment hidden due to 3 or more reports:' "\n" .
  624.                         'Posted by: ' $comment->getUser()->getDisplayName() . '(id: ' $comment->getUser()->getId() . ')' "\n" .
  625.                         'Text: ' $comment->getText());
  626.                 }
  627.                 $this->freshdeskService->createReportTicket($user, [
  628.                     'reason' => $body['reason'],
  629.                     'postId' => $post->getId(),
  630.                     'postText' => $post->getText(),
  631.                     'postLink' => $post->getDynamicLink(),
  632.                     'commentId' => $comment->getId(),
  633.                     'commentText' => $comment->getText(),
  634.                     'commentImage' => $comment->getImageUrl()
  635.                 ]);
  636.             }
  637.         }
  638.         return new JsonResponse(null200);
  639.     }
  640.     public function profileAction(Request $request): JsonResponse
  641.     {
  642.         $hash $request->get('hash');
  643.         $user $this->getUser();
  644.         if (empty($user)) {
  645.             return new JsonResponse('forbidden'403);
  646.         }
  647.         $profile $this->feedService->getUserProfile($hash$user);
  648.         if ($profile === null) {
  649.             return new JsonResponse('not_found'404);
  650.         }
  651.         $arrUser $this->userService->fetchPublicUser($hashfalsetrue);
  652.         $bgImages $this->imageService->fetchImagesByReference($arrUser['id'], ImageService::TYPE_PROFILE_GARDEN);
  653.         $profile['backgroundImages'] = [];
  654.         /** @var Image $bgImage */
  655.         foreach ($bgImages as $bgImage) {
  656.             $profile['backgroundImages'][] = [
  657.                 'imageId' => $bgImage->getId(),
  658.                 'imageUrl' => $this->imageService->getImageUrl($bgImage->getId()),
  659.             ];
  660.         }
  661.         // add interests
  662.         $profileUser $this->userService->fetchUserWithHash($hash);
  663.         $ownOnboardingData $user->getSetting('onboardingData');
  664.         $onboardingData $profileUser->getSetting('onboardingData');
  665.         if (isset($onboardingData['topicsOfInterest'])) {
  666.             $profile['topics'] = [];
  667.             $topics $onboardingData['topicsOfInterest'];
  668.             $ownTopics $ownOnboardingData['topicsOfInterest'] ?? [];
  669.             foreach ($topics as $topic) {
  670.                 $profile['topics'][] = [
  671.                     'label' => $topic,
  672.                     'isShared' => in_array($topic$ownTopics)
  673.                 ];
  674.             }
  675.         }
  676.         $favorites $profileUser->getFavoriteCrop();
  677.         $ownFavorites $user->getFavoriteCrop();
  678.         $ownFavoriteIds = [];
  679.         foreach ($ownFavorites as $ownFavorite) {
  680.             $crop $ownFavorite->getCrop();
  681.             if ($crop->getParentCrop() !== null) {
  682.                 $crop $crop->getParentCrop();
  683.             }
  684.             $ownFavoriteIds[] = $crop->getId();
  685.         }
  686.         $profile['favoriteCrops'] = [];
  687.         $usedFavoriteIds = [];
  688.         foreach ($favorites as $favorite) {
  689.             $crop $favorite->getCrop();
  690.             if ($crop->getParentCrop() !== null) {
  691.                 $crop $crop->getParentCrop();
  692.             }
  693.             if (!in_array($crop->getId(), $usedFavoriteIds)) {
  694.                 $profile['favoriteCrops'][] = [
  695.                     'id' => $crop->getId(),
  696.                     'name' => $crop->getName(),
  697.                     'isShared' => in_array($crop->getId(), $ownFavoriteIds)
  698.                 ];
  699.                 $usedFavoriteIds[] = $crop->getId();
  700.             }
  701.         }
  702.         // add shareable link to user profile
  703.         $profileLink $profileUser->getSetting(SettingConstants::PROFILE_LINK);
  704.         if (empty($profileLink)) {
  705.             $profileLink $this->dynamicLinksService->createProfileLink($profileUser->getPublicProfileHash());
  706.             $profileUser->setSetting(SettingConstants::PROFILE_LINK$profileLink);
  707.             $this->userService->save($profileUser);
  708.         }
  709.         $profile['shareLink'] = $profileLink;
  710.         return new JsonResponse($profile);
  711.     }
  712.     public function listTagsAction(Request $request): JsonResponse
  713.     {
  714.         $user $this->getUser();
  715.         if (empty($user)) {
  716.             return new JsonResponse('forbidden'403);
  717.         }
  718.         // cache result for 1h
  719.         $cacheAdapter = new ApcuAdapter('feed');
  720.         $cacheKey 'feed-tags-' $user->getId();
  721.         $tags $cacheAdapter->get($cacheKey, function (ItemInterface $item) use ($user) {
  722.             $item->expiresAfter(3600);
  723.             // list all tags that can be used in posts
  724.             return $this->feedService->fetchTags($user);
  725.         });
  726.         return new JsonResponse($tags);
  727.     }
  728.     public function categorizeAction(Request $request): JsonResponse
  729.     {
  730.         /** @var User $user */
  731.         $user $this->getUser();
  732.         if (empty($user) || !$user->isSuperModerator()) {
  733.             return new JsonResponse('forbidden'403);
  734.         }
  735.         $postId $request->get('postId');
  736.         $data json_decode($request->getContent(), true);
  737.         $category $data['category'];
  738.         $post $this->feedService->fetchPost($user$postIdnullfalse);
  739.         if (empty($post)) {
  740.             return new JsonResponse('not_found'Response::HTTP_NOT_FOUND);
  741.         }
  742.         $this->feedService->categorizePost($postId$category);
  743.         $post $this->getPost($user$postId$request);
  744.         return new JsonResponse($post);
  745.     }
  746.     public function getPostAction(Request $request): JsonResponse
  747.     {
  748.         $user $this->getUser();
  749.         if (empty($user)) {
  750.             return new JsonResponse('forbidden'Response::HTTP_FORBIDDEN);
  751.         }
  752.         $postId $request->get('postId');
  753.         $post $this->getPost($user$postId$request);
  754.         if ($post === null) {
  755.             return new JsonResponse(nullResponse::HTTP_NOT_FOUND);
  756.         }
  757.         return new JsonResponse($post);
  758.     }
  759.     /**
  760.      * @OA\Get(
  761.      *     description="Lists all users that are growing a crop with their community profile. Growing a
  762.     crop means that the user either has this crop in her patches or that she has posted in the
  763.     community with the name of the crop",
  764.      *     @OA\Response(
  765.      *         response=200,
  766.      *         description="Success",
  767.      *         @OA\JsonContent(
  768.      *             type="object",
  769.      *             @OA\Property(property="users", type="array",
  770.      *                 @OA\Items(
  771.      *                     @OA\Property(property="displayName", type="string"),
  772.      *                     @OA\Property(property="imageUrl", type="string"),
  773.      *                     @OA\Property(property="description", type="string"),
  774.      *                     @OA\Property(property="commentCount", type="string"),
  775.      *                     @OA\Property(property="links", type="array", @OA\Items(type="string"))
  776.      *                 ),
  777.      *             ),
  778.      *             @OA\Property(property="amount", type="number")
  779.      *         ),
  780.      *     ),
  781.      * )
  782.      */
  783.     public function listCommunityUsersForCrop(int $cropId): JsonResponse
  784.     {
  785.         $user $this->getUser();
  786.         if (empty($user)) {
  787.             return new JsonResponse('forbidden'Response::HTTP_FORBIDDEN);
  788.         }
  789.         $users $this->feedService->fetchUsersGrowingCrops($cropId50);
  790.         $usersWithProfile = [];
  791.         shuffle($users);
  792.         /** @var User $user */
  793.         foreach ($users as $user) {
  794.             $hash $user->getPublicProfileHash();
  795.             $filePath $this->userService->getProfileImage($hash);
  796.             if ($filePath === false || !file_exists($filePath)) {
  797.                 continue;
  798.             }
  799.             $usersWithProfile[] = $this->feedService->getUserProfile($hash$user);
  800.             if (count($usersWithProfile) >= 5) {
  801.                 break;
  802.             }
  803.         }
  804.         return new JsonResponse([
  805.             'amount' => count($users),
  806.             'users' => $usersWithProfile
  807.         ]);
  808.     }
  809.     public function followTagAction(int $tagId): JsonResponse
  810.     {
  811.         $user $this->getUser();
  812.         if (empty($user)) {
  813.             return new JsonResponse('forbidden'Response::HTTP_FORBIDDEN);
  814.         }
  815.         $result $this->tagConnectionService->saveConnection($user$tagId);
  816.         return new JsonResponse(['success' => $result]);
  817.     }
  818.     public function unfollowTagAction(int $tagId): JsonResponse
  819.     {
  820.         $user $this->getUser();
  821.         if (empty($user)) {
  822.             return new JsonResponse('forbidden'Response::HTTP_FORBIDDEN);
  823.         }
  824.         $result $this->tagConnectionService->deleteConnection($user$tagId);
  825.         return new JsonResponse(['success' => $result]);
  826.     }
  827.     public function listTagConnectionAction(): JsonResponse
  828.     {
  829.         $user $this->getUser();
  830.         if (empty($user)) {
  831.             return new JsonResponse('forbidden'Response::HTTP_FORBIDDEN);
  832.         }
  833.         $tags $this->tagConnectionService->listConnections($user);
  834.         return new JsonResponse(['tags' => $tags]);
  835.     }
  836.     public function getPostEmotionsAction(int $postId): JsonResponse
  837.     {
  838.         $user $this->getUser();
  839.         if (empty($user)) {
  840.             return new JsonResponse('forbidden'Response::HTTP_FORBIDDEN);
  841.         }
  842.         $tags $this->feedService->fetchEmotionsForPost($postId);
  843.         return new JsonResponse(['emotions' => $tags]);
  844.     }
  845.     public function getCommentEmotionsAction(int $commentId): JsonResponse
  846.     {
  847.         $user $this->getUser();
  848.         if (empty($user)) {
  849.             return new JsonResponse('forbidden'Response::HTTP_FORBIDDEN);
  850.         }
  851.         $tags $this->feedService->fetchEmotionsForComment($commentId);
  852.         return new JsonResponse(['emotions' => $tags]);
  853.     }
  854.     public function helpfulCommentAction(HelpfulCommentDto $dto): JsonResponse
  855.     {
  856.         if (empty($dto->user)) {
  857.             return new JsonResponse('forbidden'Response::HTTP_FORBIDDEN);
  858.         }
  859.         $helpful $this->feedService->markCommentAsHelpful($dto);
  860.         if ($dto->isHelpful) {
  861.             $notification $this->notificationService->createNotificationForCommentHelpful($helpful);
  862.             if ($notification) {
  863.                 $this->pushNotificationService->createPushNotifications([$notification]);
  864.             }
  865.         }
  866.         return new JsonResponse(['success' => true]);
  867.     }
  868.     public function trendingPostsAction(): JsonResponse
  869.     {
  870.         $user $this->getUser();
  871.         if (empty($user)) {
  872.             return new JsonResponse('forbidden'Response::HTTP_FORBIDDEN);
  873.         }
  874.         $hashtagFilter $this->contentService->getActiveChallengeHashtags($user);
  875.         $posts $this->feedService->fetchTrendingPosts($user$hashtagFilter);
  876.         return new JsonResponse(['posts' => $posts]);
  877.     }
  878.     private function getPost(User $userint $postIdRequest $request): ?array
  879.     {
  880.         /** @var Token $token */
  881.         $token $user->getToken()->first();
  882.         $accessToken $token->getToken();
  883.         // no app-version set means we have an older app which does not support urls in richtext
  884.         $appVersion $request->headers->get('app-version');
  885.         if (empty($appVersion)) {
  886.             $this->feedService->setFlag(FeedService::FLAG_NO_URLS_IN_RICHTEXT);
  887.         }
  888.         $isV2 $this->isApiV2($request);
  889.         return $this->feedService->fetchPost($user$postId$accessTokentrue$isV2);
  890.     }
  891.     /**
  892.      * @param $query
  893.      * @param array $filters
  894.      * @param $category
  895.      * @param $userFilter
  896.      * @return array
  897.      */
  898.     private function createFilters($query$category$userFilter): array
  899.     {
  900.         $filters = [];
  901.         if (!empty($query)) {
  902.             if (strpos($query'#') === 0) {
  903.                 $filters['tag'] = substr($query1);
  904.             } else {
  905.                 $filters['query'] = $query;
  906.             }
  907.         }
  908.         if (!empty($category)) {
  909.             $filters['category'] = $category;
  910.         } else {
  911.             $filters['category'] = 'default';
  912.         }
  913.         if (!empty($userFilter)) {
  914.             $filters['userHash'] = $userFilter;
  915.         }
  916.         return $filters;
  917.     }
  918. }