getSafe('feed_id', 'intval'); if (!$feedId) { return [ 'reactions' => [] ]; } $feed = Feed::withoutGlobalScopes()->byUserAccess(get_current_user_id())->findOrFail($feedId); $reactions = $feed->reactions() ->whereHas('xprofile') ->with([ 'xprofile' => function ($q) { $q->select(ProfileHelper::getXProfilePublicFields()); } ]) ->where('type', 'like') ->distinct('user_id') ->limit(100) ->get(); // Todo: Add lazy loading in the future return apply_filters('fluent_community/reactions_api_response', [ 'reactions' => $reactions ], $reactions, $request->all()); } public function getByCommentId(Request $request) { $commentId = $request->getSafe('comment_id', 'intval'); if (!$commentId) { return [ 'reactions' => [] ]; } $comment = Comment::findOrFail($commentId); // Just validate the permission Feed::withoutGlobalScopes()->byUserAccess(get_current_user_id())->findOrFail($comment->post_id); $reactions = $comment ->reactions() ->whereHas('xprofile') ->with([ 'xprofile' => function ($q) { $q->select(ProfileHelper::getXProfilePublicFields()); } ]) ->where('type', 'like') ->distinct('user_id') ->limit(100) ->get(); // Todo: Add lazy loading in the future return apply_filters('fluent_community/reactions_api_response', [ 'reactions' => $reactions ], $reactions, $request->all()); } public function addOrRemovePostReact(Request $request, $feed_id) { $currentUser = $this->getUser(true); $feed = Feed::withoutGlobalScopes()->byUserAccess($currentUser->ID)->findOrFail($feed_id); $type = $request->get('react_type', 'like'); $type = in_array($type, ['like', 'bookmark'], true) ? $type : 'like'; $willRemove = $request->get('remove'); if (!in_array($feed->status, FeedsHelper::getViewableByLinkStatuses(), true)) { return $this->sendError([ 'message' => __('This post is not published yet', 'fluent-community') ]); } if (!$willRemove && (int) $currentUser->ID === (int) $feed->user_id && apply_filters('fluent_community/disable_self_post_react', false, $feed)) { return $this->sendError([ 'message' => __('You cannot react to your own post', 'fluent-community') ]); } $react = Reaction::where('user_id', $currentUser->ID) ->where('object_id', $feed->id) ->where('type', $type) ->objectType('feed') ->first(); if ($willRemove) { if ($react) { $react->delete(); if ($type == 'like') { $feed->reactions_count = $feed->reactions_count - 1; $feed->timestamps = false; // Don't update the updated_at timestamp $feed->save(); do_action('fluent_community/feed/react_removed', $feed); } } return [ 'message' => __('Reaction has been removed', 'fluent-community'), 'new_count' => $feed->reactions_count ]; } if ($react) { return [ 'message' => __('You have already reacted to this post', 'fluent-community'), 'new_count' => $feed->reactions_count ]; } // Serialize a user's concurrent reactions by locking their profile row, // so parallel add requests cannot each insert a duplicate reaction. The // like counter is updated atomically, so unrelated users never contend. $react = Helper::dbTransaction(function () use ($feed, $currentUser, $type) { XProfile::where('user_id', $currentUser->ID)->lockForUpdate()->first(); $react = Reaction::where('user_id', $currentUser->ID) ->where('object_id', $feed->id) ->where('type', $type) ->objectType('feed') ->first(); if ($react) { return $react; } $react = Reaction::create([ 'user_id' => $currentUser->ID, 'object_id' => $feed->id, 'type' => $type, 'object_type' => 'feed' ]); if ($type == 'like') { // getQuery() so the atomic increment does not touch updated_at Feed::withoutGlobalScopes()->where('id', $feed->id)->getQuery()->increment('reactions_count'); $feed->reactions_count = $feed->reactions_count + 1; } return $react; }); if ($react->wasRecentlyCreated && $type == 'like') { $react->load('xprofile'); do_action('fluent_community/feed/react_added', $react, $feed); } return [ 'message' => __('Reaction has been added', 'fluent-community'), 'new_count' => $feed->reactions_count ]; } public function castSurveyVote(Request $request, $feed_id) { $userId = $this->getUserId(); $feed = Feed::byUserAccess($userId) ->where('id', $feed_id) ->first(); if (!$feed || $feed->content_type != 'survey' || !$userId) { return $this->sendError([ 'message' => __('Sorry! you do not have access to this post or invalid request', 'fluent-community') ]); } $surveyConfig = Arr::get($feed->meta, 'survey_config', []); if (empty($surveyConfig['options'])) { return $this->sendError([ 'message' => __('Sorry! This survey configuration is invalid', 'fluent-community') ]); } $endDate = Arr::get($surveyConfig, 'end_date'); if ($endDate && strtotime($endDate) < current_time('timestamp')) { return $this->sendError([ 'message' => __('Sorry! This survey has ended', 'fluent-community') ]); } $voteIndexes = (array) $request->get('vote_indexes', []); $voteIndexes = array_values(array_map('sanitize_text_field', $voteIndexes)); $feed = FeedsHelper::castSurveyVote($voteIndexes, $feed, $userId); $surveyConfig = Arr::get($feed->meta, 'survey_config', []); $options = Arr::get($surveyConfig, 'options', []); if (empty($options) || !is_array($options)) { $options = []; } $votedOptions = $feed->getSurveyCastsByUserId($userId); foreach ($options as $index => $option) { if (in_array(Arr::get($option, 'slug'), $votedOptions, true)) { $surveyConfig['options'][$index]['voted'] = true; } } $surveyConfig = apply_filters('fluent_community/survey_config_response', $surveyConfig, $feed, $userId); return [ 'survey_config' => $surveyConfig ]; } public function getSurveyVoters($feedId, $optionSlug) { $currentUserId = get_current_user_id(); $feed = Feed::withoutGlobalScopes()->byUserAccess($currentUserId)->findOrFail($feedId); $voters = $feed->surveyVotes() ->where('object_type', $optionSlug) ->whereHas('xprofile') ->with([ 'xprofile' => function ($q) { $q->select(ProfileHelper::getXProfilePublicFields()); } ]) ->limit(100) ->get(); // Todo: Add lazy loading in the future $data = [ 'voters' => $voters ]; return apply_filters('fluent_community/survey_voters_api_response', $data, $this->request->all()); } }