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/CommentsController.php +70 -39 2.7.72.10.01 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]) : [];
@@ -284,8 +298,14 @@
284 298 $feed = Feed::withoutGlobalScopes()->findOrFail($feedId);
285 299
286 300 $comment = Comment::findOrFail($commentId);
287 301
302 + if ($comment->post_id != $feed->id) {
303 + return $this->sendError([
304 + 'message' => __('Invalid comment', 'fluent-community')
305 + ]);
306 + }
307 +
288 308 $user = $this->getUser(true);
289 309
290 310 $isMod = $user && $user->hasPermissionOrInCurrentSpace('community_moderator', $feed->space);
291 311 $isAdmin = $user && $user->hasPermissionOrInCurrentSpace('community_admin', $feed->space);
@@ -420,9 +440,9 @@
420 440 }
421 441
422 442 private function validateCommentText($data)
423 443 {
424 - $text = trim(Arr::get($data, 'comment'));
444 + $text = trim((string) Arr::get($data, 'comment', ''));
425 445 $text = CustomSanitizer::unslashMarkdown($text);
426 446
427 447 // Decode HTML entities (e.g.,   for space) and strip all whitespace for validation
428 448 $textForValidation = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
@@ -449,9 +469,9 @@
449 469 }
450 470
451 471 private function verifyCreateCommentPermission($feed)
452 472 {
453 - if (Arr::get($feed->meta, 'comments_disabled') === 'yes') {
473 + if (!FeedsHelper::commentsEnabled($feed)) {
454 474 throw new \Exception(esc_html__('Comments are disabled for this post', 'fluent-community'));
455 475 }
456 476
457 477 $this->verifySpacePermission($feed);
@@ -500,17 +520,18 @@
500 520 {
501 521 $userId = get_current_user_id();
502 522 $feed = Feed::withoutGlobalScopes()->byUserAccess($userId)->findOrFail($feed_id);
503 523 $type = $request->get('react_type', 'like');
524 + $type = in_array($type, ['like', 'bookmark'], true) ? $type : 'like';
504 525 $willRemove = $request->get('remove');
505 526
506 - if ($feed->status != 'published') {
527 + if (!in_array($feed->status, FeedsHelper::getViewableByLinkStatuses(), true)) {
507 528 return $this->sendError([
508 529 'message' => __('This post is not published yet', 'fluent-community')
509 530 ]);
510 531 }
511 532
512 - if ($userId === $feed->user_id && apply_filters('fluent_community/disable_self_post_react', false, $feed)) {
533 + if (!$willRemove && (int) $userId === (int) $feed->user_id && apply_filters('fluent_community/disable_self_post_react', false, $feed)) {
513 534 return $this->sendError([
514 535 'message' => __('You cannot react to your own post', 'fluent-community')
515 536 ]);
516 537 }
@@ -606,9 +627,9 @@
606 627 }
607 628
608 629 public function toggleReaction(Request $request, $feedId, $commentId)
609 630 {
610 - $feed = Feed::withoutGlobalScopes()->findOrFail($feedId);
631 + $feed = Feed::withoutGlobalScopes()->byUserAccess(get_current_user_id())->findOrFail($feedId);
611 632 $comment = Comment::findOrFail($commentId);
612 633
613 634 if ($comment->post_id != $feed->id) {
614 635 return $this->sendError([
@@ -622,28 +643,38 @@
622 643 $user->verifySpacePermission('registered', $feed->space);
623 644 }
624 645
625 646 $userId = get_current_user_id();
626 - if ($userId === $comment->user_id && apply_filters('fluent_community/disable_self_comment_react', false, $feed)) {
647 + $reactionState = !!$request->get('state', false);
648 +
649 + if ($reactionState && (int) $userId === (int) $comment->user_id && apply_filters('fluent_community/disable_self_comment_react', false, $feed)) {
627 650 return $this->sendError([
628 651 'message' => __('You cannot react to your own comment', 'fluent-community')
629 652 ]);
630 653 }
631 654
632 - $reactionState = !!$request->get('state', false);
655 + if ($reactionState) {
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();
633 660
634 - if ($reactionState) {
635 - // add or update the reaction
636 - $reaction = Reaction::firstOrCreate([
637 - 'user_id' => get_current_user_id(),
638 - 'object_id' => $comment->id,
639 - 'object_type' => 'comment',
640 - 'parent_id' => $feed->id
641 - ]);
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 + ]);
642 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 +
643 676 if ($reaction->wasRecentlyCreated) {
644 - $comment->reactions_count = $comment->reactions_count + 1;
645 - $comment->save();
646 677 do_action('fluent_community/comment/react_added', $reaction, $comment, $feed);
647 678 }
648 679 } else {
649 680 // remove the reaction