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