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 +47 -9 2.7.02.10.0 View file →
@@ -4,9 +4,11 @@
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;
11 13 use FluentCommunity\Framework\Support\Arr;
12 14
@@ -78,10 +80,23 @@
78 80 {
79 81 $currentUser = $this->getUser(true);
80 82 $feed = Feed::withoutGlobalScopes()->byUserAccess($currentUser->ID)->findOrFail($feed_id);
81 83 $type = $request->get('react_type', 'like');
84 + $type = in_array($type, ['like', 'bookmark'], true) ? $type : 'like';
82 85 $willRemove = $request->get('remove');
83 86
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 +
84 99 $react = Reaction::where('user_id', $currentUser->ID)
85 100 ->where('object_id', $feed->id)
86 101 ->where('type', $type)
87 102 ->objectType('feed')
@@ -91,8 +106,9 @@
91 106 if ($react) {
92 107 $react->delete();
93 108 if ($type == 'like') {
94 109 $feed->reactions_count = $feed->reactions_count - 1;
110 + $feed->timestamps = false; // Don't update the updated_at timestamp
95 111 $feed->save();
96 112 do_action('fluent_community/feed/react_removed', $feed);
97 113 }
98 114 }
@@ -109,19 +125,41 @@
109 125 'new_count' => $feed->reactions_count
110 126 ];
111 127 }
112 128
113 - $react = Reaction::create([
114 - 'user_id' => $currentUser->ID,
115 - 'object_id' => $feed->id,
116 - 'type' => $type,
117 - 'object_type' => 'feed'
118 - ]);
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();
119 134
120 - if ($type == 'like') {
121 - $feed->reactions_count = $feed->reactions_count + 1;
122 - $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();
123 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') {
124 162 $react->load('xprofile');
125 163 do_action('fluent_community/feed/react_added', $react, $feed);
126 164 }
127 165