PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.11.0
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.11.0
2.11.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 All 78 releases
← All changes | app/Http/Controllers/CommentsController.php +68 -36 2.8.02.11.0 View file →
@@ -11,8 +11,9 @@
11 11 use FluentCommunity\Framework\Http\Request\Request;
12 12 use FluentCommunity\App\Models\Comment;
13 13 use FluentCommunity\App\Models\Feed;
14 14 use FluentCommunity\App\Models\Reaction;
15 +use FluentCommunity\App\Models\XProfile;
15 16 use FluentCommunity\Framework\Support\Arr;
16 17
17 18 class CommentsController extends Controller
18 19 {
@@ -21,15 +22,24 @@
21 22 $feed = Feed::withoutGlobalScopes()
22 23 ->byUserAccess(get_current_user_id())
23 24 ->findOrFail($feed_id);
24 25
25 - if ($feed->status != 'published' && !$feed->hasEditAccess($this->getUserId())) {
26 + if (!in_array($feed->status, FeedsHelper::getViewableByLinkStatuses(), true) && !$feed->hasEditAccess($this->getUserId())) {
26 27 return $this->sendError([
27 28 'message' => __('Sorry, you do not have permission to view this post', 'fluent-community')
28 29 ], 404);
29 30 }
30 31
31 - $canViewComments = apply_filters('fluent_community/can_view_comments_' . $feed->type, true, $feed);
32 + /*
33 + * The row's own setting is the default the filter gets handed, rather than a bare
34 + * true. Before this, meta.enable_comments was read nowhere on this path, so a page
35 + * with comments switched off still served its thread to anyone who asked for it.
36 + */
37 + $canViewComments = apply_filters(
38 + 'fluent_community/can_view_comments_' . $feed->type,
39 + FeedsHelper::commentsEnabled($feed),
40 + $feed
41 + );
32 42
33 43 if (!$canViewComments) {
34 44 return [
35 45 'comments' => []
@@ -78,9 +88,9 @@
78 88
79 89 $text = $this->validateCommentText($request->all());
80 90 $feed = Feed::withoutGlobalScopes()->findOrFail($feedId);
81 91
82 - if ($feed->status != 'published') {
92 + if (!in_array($feed->status, FeedsHelper::getViewableByLinkStatuses(), true)) {
83 93 return $this->sendError([
84 94 'message' => __('This post is not published yet', 'fluent-community')
85 95 ]);
86 96 }
@@ -88,25 +98,8 @@
88 98 $this->verifyCreateCommentPermission($feed);
89 99
90 100 $requestData = $request->all();
91 101
92 - // Check for duplicate (only for comments with text)
93 - if ($text) {
94 - $skipDuplicateCheck = apply_filters('fluent_community/disable_duplicate_comment_check', false, get_current_user_id(), $feed->id);
95 - if (!$skipDuplicateCheck) {
96 - $exist = Comment::where('user_id', get_current_user_id())
97 - ->where('message', $text)
98 - ->where('post_id', $feed->id)
99 - ->first();
100 -
101 - if ($exist) {
102 - return $this->sendError([
103 - 'message' => __('No duplicate comment please!', 'fluent-community')
104 - ]);
105 - }
106 - }
107 - }
108 -
109 102 [$markdown, $inlineMedias] = FeedsHelper::replaceImageUrlsWithRealMediaArchive($text);
110 103 $mentions = FeedsHelper::getMentions($markdown, $feed->space_id, true);
111 104 $commentHtml = $this->generateCommentHtml($markdown, $mentions);
112 105
@@ -138,14 +131,41 @@
138 131 do_action('fluent_community/before_comment_create', $commentData, $feed);
139 132
140 133 $commentData = apply_filters('fluent_community/comment/comment_data', $commentData, $feed);
141 134
142 - $comment = Comment::create($commentData);
135 + // Only comments with text are duplicate checked
136 + $shouldCheckDuplicate = $text && !apply_filters('fluent_community/disable_duplicate_comment_check', false, get_current_user_id(), $feed->id);
143 137
144 - $feed->comments_count = $feed->comments_count + 1;
145 - $feed->save();
138 + // Serialize a member's concurrent submissions by locking their profile row,
139 + // so parallel matching requests cannot pass the duplicate check and both insert.
140 + $comment = Helper::dbTransaction(function () use ($commentData, $feed, $text, $shouldCheckDuplicate) {
141 + XProfile::where('user_id', get_current_user_id())->lockForUpdate()->first();
146 142
143 + if ($shouldCheckDuplicate && Comment::where('user_id', get_current_user_id())->where('message', $text)->where('post_id', $feed->id)->first()) {
144 + return null;
145 + }
147 146
147 + $newComment = Comment::create($commentData);
148 +
149 + // A held comment is not visible yet, so it must not be counted until it is approved.
150 + if ($newComment->status === 'published') {
151 + Feed::withoutGlobalScopes()->where('id', $feed->id)->increment('comments_count');
152 + }
153 +
154 + return $newComment;
155 + });
156 +
157 + if (!$comment) {
158 + return $this->sendError([
159 + 'message' => __('No duplicate comment please!', 'fluent-community')
160 + ]);
161 + }
162 +
163 + if ($comment->status === 'published') {
164 + $feed->comments_count = $feed->comments_count + 1;
165 + }
166 +
167 +
148 168 // Merge and save all media in one loop
149 169 $mediaItems = $mediaItems ? (is_array($mediaItems) ? $mediaItems : [$mediaItems]) : [];
150 170
151 171 if ($inlineMedias) {
@@ -426,9 +446,9 @@
426 446 }
427 447
428 448 private function validateCommentText($data)
429 449 {
430 - $text = trim(Arr::get($data, 'comment'));
450 + $text = trim((string) Arr::get($data, 'comment', ''));
431 451 $text = CustomSanitizer::unslashMarkdown($text);
432 452
433 453 // Decode HTML entities (e.g.,   for space) and strip all whitespace for validation
434 454 $textForValidation = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
@@ -455,9 +475,9 @@
455 475 }
456 476
457 477 private function verifyCreateCommentPermission($feed)
458 478 {
459 - if (Arr::get($feed->meta, 'comments_disabled') === 'yes') {
479 + if (!FeedsHelper::commentsEnabled($feed)) {
460 480 throw new \Exception(esc_html__('Comments are disabled for this post', 'fluent-community'));
461 481 }
462 482
463 483 $this->verifySpacePermission($feed);
@@ -509,9 +529,9 @@
509 529 $type = $request->get('react_type', 'like');
510 530 $type = in_array($type, ['like', 'bookmark'], true) ? $type : 'like';
511 531 $willRemove = $request->get('remove');
512 532
513 - if ($feed->status != 'published') {
533 + if (!in_array($feed->status, FeedsHelper::getViewableByLinkStatuses(), true)) {
514 534 return $this->sendError([
515 535 'message' => __('This post is not published yet', 'fluent-community')
516 536 ]);
517 537 }
@@ -599,9 +619,11 @@
599 619 }
600 620
601 621 $comment->delete();
602 622
603 - $feed->comments_count = Comment::where('post_id', $feed->id)->count();
623 + $feed->comments_count = Comment::where('post_id', $feed->id)
624 + ->where('status', 'published')
625 + ->count();
604 626 $feed->timestamps = false; // Don't update the updated_at timestamp
605 627 $feed->save();
606 628
607 629 do_action('fluent_community/comment_deleted_' . $feed->type, $commentId, $feed);
@@ -638,19 +660,29 @@
638 660 ]);
639 661 }
640 662
641 663 if ($reactionState) {
642 - // add or update the reaction
643 - $reaction = Reaction::firstOrCreate([
644 - 'user_id' => get_current_user_id(),
645 - 'object_id' => $comment->id,
646 - 'object_type' => 'comment',
647 - 'parent_id' => $feed->id
648 - ]);
664 + // Serialize concurrent reactions on this comment by locking its row,
665 + // so parallel add requests cannot each insert a duplicate reaction.
666 + $reaction = Helper::dbTransaction(function () use ($comment, $feed) {
667 + XProfile::where('user_id', get_current_user_id())->lockForUpdate()->first();
649 668
669 + $reaction = Reaction::firstOrCreate([
670 + 'user_id' => get_current_user_id(),
671 + 'object_id' => $comment->id,
672 + 'object_type' => 'comment',
673 + 'parent_id' => $feed->id
674 + ]);
675 +
676 + if ($reaction->wasRecentlyCreated) {
677 + Comment::where('id', $comment->id)->increment('reactions_count');
678 + $comment->reactions_count = $comment->reactions_count + 1;
679 + }
680 +
681 + return $reaction;
682 + });
683 +
650 684 if ($reaction->wasRecentlyCreated) {
651 - $comment->reactions_count = $comment->reactions_count + 1;
652 - $comment->save();
653 685 do_action('fluent_community/comment/react_added', $reaction, $comment, $feed);
654 686 }
655 687 } else {
656 688 // remove the reaction