PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.10.01
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.10.01
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 +96 -29 1.1.02.10.01 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)
@@ -34,11 +37,11 @@
34 37 ->distinct('user_id')
35 38 ->limit(100)
36 39 ->get(); // Todo: Add lazy loading in the future
37 40
38 - return [
41 + return apply_filters('fluent_community/reactions_api_response', [
39 42 'reactions' => $reactions
40 - ];
43 + ], $reactions, $request->all());
41 44 }
42 45
43 46 public function getByCommentId(Request $request)
44 47 {
@@ -52,9 +55,9 @@
52 55
53 56 $comment = Comment::findOrFail($commentId);
54 57
55 58 // Just validate the permission
56 - $feed = Feed::withoutGlobalScopes()->byUserAccess(get_current_user_id())->findOrFail($comment->post_id);
59 + Feed::withoutGlobalScopes()->byUserAccess(get_current_user_id())->findOrFail($comment->post_id);
57 60
58 61 $reactions = $comment
59 62 ->reactions()
60 63 ->whereHas('xprofile')
@@ -67,20 +70,34 @@
67 70 ->distinct('user_id')
68 71 ->limit(100)
69 72 ->get(); // Todo: Add lazy loading in the future
70 73
71 - return [
74 + return apply_filters('fluent_community/reactions_api_response', [
72 75 'reactions' => $reactions
73 - ];
76 + ], $reactions, $request->all());
74 77 }
75 78
76 79 public function addOrRemovePostReact(Request $request, $feed_id)
77 80 {
78 - $feed = Feed::withoutGlobalScopes()->findOrFail($feed_id);
81 + $currentUser = $this->getUser(true);
82 + $feed = Feed::withoutGlobalScopes()->byUserAccess($currentUser->ID)->findOrFail($feed_id);
79 83 $type = $request->get('react_type', 'like');
84 + $type = in_array($type, ['like', 'bookmark'], true) ? $type : 'like';
80 85 $willRemove = $request->get('remove');
81 86
82 - $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)
83 100 ->where('object_id', $feed->id)
84 101 ->where('type', $type)
85 102 ->objectType('feed')
86 103 ->first();
@@ -89,14 +106,16 @@
89 106 if ($react) {
90 107 $react->delete();
91 108 if ($type == 'like') {
92 109 $feed->reactions_count = $feed->reactions_count - 1;
110 + $feed->timestamps = false; // Don't update the updated_at timestamp
93 111 $feed->save();
112 + do_action('fluent_community/feed/react_removed', $feed);
94 113 }
95 114 }
96 115
97 116 return [
98 - 'message' => 'Reaction has been removed',
117 + 'message' => __('Reaction has been removed', 'fluent-community'),
99 118 'new_count' => $feed->reactions_count
100 119 ];
101 120 }
102 121
@@ -101,30 +120,52 @@
101 120 }
102 121
103 122 if ($react) {
104 123 return [
105 - 'message' => 'You have already reacted to this post',
124 + 'message' => __('You have already reacted to this post', 'fluent-community'),
106 125 'new_count' => $feed->reactions_count
107 126 ];
108 127 }
109 128
110 - $react = Reaction::create([
111 - 'user_id' => get_current_user_id(),
112 - 'object_id' => $feed->id,
113 - 'type' => $type,
114 - 'object_type' => 'feed'
115 - ]);
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();
116 134
117 - if ($type == 'like') {
118 - $feed->reactions_count = $feed->reactions_count + 1;
119 - $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();
120 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') {
121 162 $react->load('xprofile');
122 163 do_action('fluent_community/feed/react_added', $react, $feed);
123 164 }
124 165
125 166 return [
126 - 'message' => 'Reaction has been added',
167 + 'message' => __('Reaction has been added', 'fluent-community'),
127 168 'new_count' => $feed->reactions_count
128 169 ];
129 170 }
130 171
@@ -129,30 +170,55 @@
129 170 }
130 171
131 172 public function castSurveyVote(Request $request, $feed_id)
132 173 {
133 - $feed = Feed::where('id', $feed_id)
134 - ->byUserAccess($this->getUserId())
174 + $userId = $this->getUserId();
175 +
176 + $feed = Feed::byUserAccess($userId)
177 + ->where('id', $feed_id)
135 178 ->first();
136 179
137 - if (!$feed || $feed->content_type != 'survey') {
180 + if (!$feed || $feed->content_type != 'survey' || !$userId) {
138 181 return $this->sendError([
139 182 'message' => __('Sorry! you do not have access to this post or invalid request', 'fluent-community')
140 183 ]);
141 184 }
142 185
143 - $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 + }
144 192
145 - $feed = FeedsHelper::castSurveyVote($voteIndexes, $feed, $this->getUserId());
146 - $surveyConfig = $feed->meta['survey_config'];
147 - $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 + }
148 199
149 - foreach ($surveyConfig['options'] as $index => $option) {
150 - 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)) {
151 215 $surveyConfig['options'][$index]['voted'] = true;
152 216 }
153 217 }
154 218
219 + $surveyConfig = apply_filters('fluent_community/survey_config_response', $surveyConfig, $feed, $userId);
220 +
155 221 return [
156 222 'survey_config' => $surveyConfig
157 223 ];
158 224 }
@@ -173,9 +239,10 @@
173 239 ])
174 240 ->limit(100)
175 241 ->get(); // Todo: Add lazy loading in the future
176 242
177 - return [
243 + $data = [
178 244 'voters' => $voters
179 245 ];
246 + return apply_filters('fluent_community/survey_voters_api_response', $data, $this->request->all());
180 247 }
181 248 }