# fluent-community/trunk/app/Http/Controllers/ReactionController.php

FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS &amp; Online Courses, version trunk. 249 lines.

- Page: https://pluginprobe.com/plugins/fluent-community/trunk/code/app/Http/Controllers/ReactionController.php
- Raw: https://pluginprobe.com/plugins/fluent-community/trunk/raw/app/Http/Controllers/ReactionController.php
- Modified: 2026-09-03T15:28:12+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/fluent-community/trunk/code/app/Http/Controllers/ReactionController.php#L10-L20`.

```php
<?php

namespace FluentCommunity\App\Http\Controllers;

use FluentCommunity\App\Models\Comment;
use FluentCommunity\App\Models\Feed;
use FluentCommunity\App\Models\Reaction;
use FluentCommunity\App\Models\XProfile;
use FluentCommunity\App\Services\FeedsHelper;
use FluentCommunity\App\Services\Helper;
use FluentCommunity\App\Services\ProfileHelper;
use FluentCommunity\Framework\Http\Request\Request;
use FluentCommunity\Framework\Support\Arr;

class ReactionController extends Controller
{
    public function getByFeedId(Request $request)
    {
        $feedId = $request->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());
    }
}

```
