PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.10.0
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.10.0
2.10.0 2.10.01 2.9.1 2.9.0 2.8.1 2.8.0 2.7.7 2.7.5 2.7.0 2.6.01 2.6.0 2.5.0 2.4.01 trunk 1.0.90 1.0.91 1.0.92 1.0.93 1.0.94 1.0.95 1.0.96 1.0.97 1.0.98 1.0.99 1.1.0 All 77 releases
← All changes | app/Http/Controllers/ReactionController.php +100 -30 1.0.932.10.0 View file →
@@ -4,11 +4,14 @@
4 4
5 5 use FluentCommunity\App\Models\Comment;
6 6 use FluentCommunity\App\Models\Feed;
7 7 use FluentCommunity\App\Models\Reaction;
8 +use FluentCommunity\App\Models\XProfile;
8 9 use FluentCommunity\App\Services\FeedsHelper;
10 +use FluentCommunity\App\Services\Helper;
9 11 use FluentCommunity\App\Services\ProfileHelper;
10 12 use FluentCommunity\Framework\Http\Request\Request;
13 +use FluentCommunity\Framework\Support\Arr;
11 14
12 15 class ReactionController extends Controller
13 16 {
14 17 public function getByFeedId(Request $request)
@@ -29,14 +32,16 @@
29 32 'xprofile' => function ($q) {
30 33 $q->select(ProfileHelper::getXProfilePublicFields());
31 34 }
32 35 ])
36 + ->where('type', 'like')
37 + ->distinct('user_id')
33 38 ->limit(100)
34 39 ->get(); // Todo: Add lazy loading in the future
35 40
36 - return [
41 + return apply_filters('fluent_community/reactions_api_response', [
37 42 'reactions' => $reactions
38 - ];
43 + ], $reactions, $request->all());
39 44 }
40 45
41 46 public function getByCommentId(Request $request)
42 47 {
@@ -50,9 +55,9 @@
50 55
51 56 $comment = Comment::findOrFail($commentId);
52 57
53 58 // Just validate the permission
54 - $feed = Feed::withoutGlobalScopes()->byUserAccess(get_current_user_id())->findOrFail($comment->post_id);
59 + Feed::withoutGlobalScopes()->byUserAccess(get_current_user_id())->findOrFail($comment->post_id);
55 60
56 61 $reactions = $comment
57 62 ->reactions()
58 63 ->whereHas('xprofile')
@@ -60,23 +65,39 @@
60 65 'xprofile' => function ($q) {
61 66 $q->select(ProfileHelper::getXProfilePublicFields());
62 67 }
63 68 ])
69 + ->where('type', 'like')
70 + ->distinct('user_id')
64 71 ->limit(100)
65 72 ->get(); // Todo: Add lazy loading in the future
66 73
67 - return [
74 + return apply_filters('fluent_community/reactions_api_response', [
68 75 'reactions' => $reactions
69 - ];
76 + ], $reactions, $request->all());
70 77 }
71 78
72 79 public function addOrRemovePostReact(Request $request, $feed_id)
73 80 {
74 - $feed = Feed::withoutGlobalScopes()->findOrFail($feed_id);
81 + $currentUser = $this->getUser(true);
82 + $feed = Feed::withoutGlobalScopes()->byUserAccess($currentUser->ID)->findOrFail($feed_id);
75 83 $type = $request->get('react_type', 'like');
84 + $type = in_array($type, ['like', 'bookmark'], true) ? $type : 'like';
76 85 $willRemove = $request->get('remove');
77 86
78 - $react = Reaction::where('user_id', get_current_user_id())
87 + if (!in_array($feed->status, FeedsHelper::getViewableByLinkStatuses(), true)) {
88 + return $this->sendError([
89 + 'message' => __('This post is not published yet', 'fluent-community')
90 + ]);
91 + }
92 +
93 + if (!$willRemove && (int) $currentUser->ID === (int) $feed->user_id && apply_filters('fluent_community/disable_self_post_react', false, $feed)) {
94 + return $this->sendError([
95 + 'message' => __('You cannot react to your own post', 'fluent-community')
96 + ]);
97 + }
98 +
99 + $react = Reaction::where('user_id', $currentUser->ID)
79 100 ->where('object_id', $feed->id)
80 101 ->where('type', $type)
81 102 ->objectType('feed')
82 103 ->first();
@@ -83,17 +104,18 @@
83 104
84 105 if ($willRemove) {
85 106 if ($react) {
86 107 $react->delete();
87 -
88 108 if ($type == 'like') {
89 109 $feed->reactions_count = $feed->reactions_count - 1;
110 + $feed->timestamps = false; // Don't update the updated_at timestamp
90 111 $feed->save();
112 + do_action('fluent_community/feed/react_removed', $feed);
91 113 }
92 114 }
93 115
94 116 return [
95 - 'message' => 'Reaction has been removed',
117 + 'message' => __('Reaction has been removed', 'fluent-community'),
96 118 'new_count' => $feed->reactions_count
97 119 ];
98 120 }
99 121
@@ -98,30 +120,52 @@
98 120 }
99 121
100 122 if ($react) {
101 123 return [
102 - 'message' => 'You have already reacted to this post',
124 + 'message' => __('You have already reacted to this post', 'fluent-community'),
103 125 'new_count' => $feed->reactions_count
104 126 ];
105 127 }
106 128
107 - $react = Reaction::create([
108 - 'user_id' => get_current_user_id(),
109 - 'object_id' => $feed->id,
110 - 'type' => $type,
111 - 'object_type' => 'feed'
112 - ]);
129 + // Serialize a user's concurrent reactions by locking their profile row,
130 + // so parallel add requests cannot each insert a duplicate reaction. The
131 + // like counter is updated atomically, so unrelated users never contend.
132 + $react = Helper::dbTransaction(function () use ($feed, $currentUser, $type) {
133 + XProfile::where('user_id', $currentUser->ID)->lockForUpdate()->first();
113 134
114 - if ($type == 'like') {
115 - $feed->reactions_count = $feed->reactions_count + 1;
116 - $feed->save();
135 + $react = Reaction::where('user_id', $currentUser->ID)
136 + ->where('object_id', $feed->id)
137 + ->where('type', $type)
138 + ->objectType('feed')
139 + ->first();
117 140
141 + if ($react) {
142 + return $react;
143 + }
144 +
145 + $react = Reaction::create([
146 + 'user_id' => $currentUser->ID,
147 + 'object_id' => $feed->id,
148 + 'type' => $type,
149 + 'object_type' => 'feed'
150 + ]);
151 +
152 + if ($type == 'like') {
153 + // getQuery() so the atomic increment does not touch updated_at
154 + Feed::withoutGlobalScopes()->where('id', $feed->id)->getQuery()->increment('reactions_count');
155 + $feed->reactions_count = $feed->reactions_count + 1;
156 + }
157 +
158 + return $react;
159 + });
160 +
161 + if ($react->wasRecentlyCreated && $type == 'like') {
118 162 $react->load('xprofile');
119 163 do_action('fluent_community/feed/react_added', $react, $feed);
120 164 }
121 165
122 166 return [
123 - 'message' => 'Reaction has been added',
167 + 'message' => __('Reaction has been added', 'fluent-community'),
124 168 'new_count' => $feed->reactions_count
125 169 ];
126 170 }
127 171
@@ -126,30 +170,55 @@
126 170 }
127 171
128 172 public function castSurveyVote(Request $request, $feed_id)
129 173 {
130 - $feed = Feed::where('id', $feed_id)
131 - ->byUserAccess($this->getUserId())
174 + $userId = $this->getUserId();
175 +
176 + $feed = Feed::byUserAccess($userId)
177 + ->where('id', $feed_id)
132 178 ->first();
133 179
134 - if (!$feed || $feed->content_type != 'survey') {
180 + if (!$feed || $feed->content_type != 'survey' || !$userId) {
135 181 return $this->sendError([
136 182 'message' => __('Sorry! you do not have access to this post or invalid request', 'fluent-community')
137 183 ]);
138 184 }
139 185
140 - $voteIndexes = $request->get('vote_indexes', []);
186 + $surveyConfig = Arr::get($feed->meta, 'survey_config', []);
187 + if (empty($surveyConfig['options'])) {
188 + return $this->sendError([
189 + 'message' => __('Sorry! This survey configuration is invalid', 'fluent-community')
190 + ]);
191 + }
141 192
142 - $feed = FeedsHelper::castSurveyVote($voteIndexes, $feed, $this->getUserId());
143 - $surveyConfig = $feed->meta['survey_config'];
144 - $votedOptions = $feed->getSurveyCastsByUserId($this->getUserId());
193 + $endDate = Arr::get($surveyConfig, 'end_date');
194 + if ($endDate && strtotime($endDate) < current_time('timestamp')) {
195 + return $this->sendError([
196 + 'message' => __('Sorry! This survey has ended', 'fluent-community')
197 + ]);
198 + }
145 199
146 - foreach ($surveyConfig['options'] as $index => $option) {
147 - if (in_array($option['slug'], $votedOptions)) {
200 + $voteIndexes = (array) $request->get('vote_indexes', []);
201 +
202 + $voteIndexes = array_values(array_map('sanitize_text_field', $voteIndexes));
203 +
204 + $feed = FeedsHelper::castSurveyVote($voteIndexes, $feed, $userId);
205 + $surveyConfig = Arr::get($feed->meta, 'survey_config', []);
206 + $options = Arr::get($surveyConfig, 'options', []);
207 + if (empty($options) || !is_array($options)) {
208 + $options = [];
209 + }
210 +
211 + $votedOptions = $feed->getSurveyCastsByUserId($userId);
212 +
213 + foreach ($options as $index => $option) {
214 + if (in_array(Arr::get($option, 'slug'), $votedOptions, true)) {
148 215 $surveyConfig['options'][$index]['voted'] = true;
149 216 }
150 217 }
151 218
219 + $surveyConfig = apply_filters('fluent_community/survey_config_response', $surveyConfig, $feed, $userId);
220 +
152 221 return [
153 222 'survey_config' => $surveyConfig
154 223 ];
155 224 }
@@ -170,9 +239,10 @@
170 239 ])
171 240 ->limit(100)
172 241 ->get(); // Todo: Add lazy loading in the future
173 242
174 - return [
243 + $data = [
175 244 'voters' => $voters
176 245 ];
246 + return apply_filters('fluent_community/survey_voters_api_response', $data, $this->request->all());
177 247 }
178 248 }