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/CommentsController.php +58 -34 2.8.02.10.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,12 +131,33 @@
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
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();
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 + }
146 +
147 + $newComment = Comment::create($commentData);
148 + Feed::withoutGlobalScopes()->where('id', $feed->id)->increment('comments_count');
149 +
150 + return $newComment;
151 + });
152 +
153 + if (!$comment) {
154 + return $this->sendError([
155 + 'message' => __('No duplicate comment please!', 'fluent-community')
156 + ]);
157 + }
158 +
144 159 $feed->comments_count = $feed->comments_count + 1;
145 - $feed->save();
146 160
147 161
148 162 // Merge and save all media in one loop
149 163 $mediaItems = $mediaItems ? (is_array($mediaItems) ? $mediaItems : [$mediaItems]) : [];
@@ -426,9 +440,9 @@
426 440 }
427 441
428 442 private function validateCommentText($data)
429 443 {
430 - $text = trim(Arr::get($data, 'comment'));
444 + $text = trim((string) Arr::get($data, 'comment', ''));
431 445 $text = CustomSanitizer::unslashMarkdown($text);
432 446
433 447 // Decode HTML entities (e.g.,   for space) and strip all whitespace for validation
434 448 $textForValidation = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
@@ -455,9 +469,9 @@
455 469 }
456 470
457 471 private function verifyCreateCommentPermission($feed)
458 472 {
459 - if (Arr::get($feed->meta, 'comments_disabled') === 'yes') {
473 + if (!FeedsHelper::commentsEnabled($feed)) {
460 474 throw new \Exception(esc_html__('Comments are disabled for this post', 'fluent-community'));
461 475 }
462 476
463 477 $this->verifySpacePermission($feed);
@@ -509,9 +523,9 @@
509 523 $type = $request->get('react_type', 'like');
510 524 $type = in_array($type, ['like', 'bookmark'], true) ? $type : 'like';
511 525 $willRemove = $request->get('remove');
512 526
513 - if ($feed->status != 'published') {
527 + if (!in_array($feed->status, FeedsHelper::getViewableByLinkStatuses(), true)) {
514 528 return $this->sendError([
515 529 'message' => __('This post is not published yet', 'fluent-community')
516 530 ]);
517 531 }
@@ -638,19 +652,29 @@
638 652 ]);
639 653 }
640 654
641 655 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 - ]);
656 + // Serialize concurrent reactions on this comment by locking its row,
657 + // so parallel add requests cannot each insert a duplicate reaction.
658 + $reaction = Helper::dbTransaction(function () use ($comment, $feed) {
659 + XProfile::where('user_id', get_current_user_id())->lockForUpdate()->first();
649 660
661 + $reaction = Reaction::firstOrCreate([
662 + 'user_id' => get_current_user_id(),
663 + 'object_id' => $comment->id,
664 + 'object_type' => 'comment',
665 + 'parent_id' => $feed->id
666 + ]);
667 +
668 + if ($reaction->wasRecentlyCreated) {
669 + Comment::where('id', $comment->id)->increment('reactions_count');
670 + $comment->reactions_count = $comment->reactions_count + 1;
671 + }
672 +
673 + return $reaction;
674 + });
675 +
650 676 if ($reaction->wasRecentlyCreated) {
651 - $comment->reactions_count = $comment->reactions_count + 1;
652 - $comment->save();
653 677 do_action('fluent_community/comment/react_added', $reaction, $comment, $feed);
654 678 }
655 679 } else {
656 680 // remove the reaction