FEATURE: Notify responders of post removal (#15049)

FEATURE: Notify responders of post removal (#15049)

  • Notify users whose posts were cascade deleted due to a flagged post
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 6db14aa..c090367 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -1498,6 +1498,7 @@ en:
     tl2_post_edit_time_limit: "A tl2+ author can edit their post for (n) minutes after posting. Set to 0 for forever."
     edit_history_visible_to_public: "Allow everyone to see previous versions of an edited post. When disabled, only staff members can view."
     delete_removed_posts_after: "Posts removed by the author will be automatically deleted after (n) hours. If set to 0, posts will be deleted immediately."
+    notify_users_after_responses_deleted_on_flagged_post: "When a post is flagged and then removed, all users that responded to the post and had their responses removed will be notified."
     max_image_width: "Maximum thumbnail width of images in a post"
     max_image_height: "Maximum thumbnail height of images in a post"
     responsive_post_image_sizes: "Resize lightbox preview images to allow for high DPI screens of the following pixel ratios. Remove all values to disable responsive images."
@@ -2791,6 +2792,11 @@ en:
     inappropriate: "Your post was flagged as **inappropriate**: the community feels it is offensive, abusive, or a violation of [our community guidelines](%{base_path}/guidelines)."
     spam: "Your post was flagged as **spam**: the community feels it is an advertisement, something that is overly promotional in nature instead of being useful or relevant to the topic as expected."
     notify_moderators: "Your post was flagged **for moderator attention**: the community feels something about the post requires manual intervention by a staff member."
+    responder:
+      off_topic: "The post was flagged as **off-topic**: the community feels it is not a good fit for the topic, as currently defined by the title and the first post."
+      inappropriate: "The post was flagged as **inappropriate**: the community feels it is offensive, abusive, or a violation of [our community guidelines](%{base_path}/guidelines)."
+      spam: "The post was flagged as **spam**: the community feels it is an advertisement, something that is overly promotional in nature instead of being useful or relevant to the topic as expected."
+      notify_moderators: "The post was flagged **for moderator attention**: the community feels something about the post requires manual intervention by a staff member."
 
   flags_dispositions:
     agreed: "Thanks for letting us know. We agree there is an issue and we're looking into it."
@@ -2889,6 +2895,24 @@ en:
 
         Please review our [community guidelines](%{base_url}/guidelines) for details.
 
+    flags_agreed_and_post_deleted_for_responders:
+      title: "Reply removed from flagged post by staff"
+      subject_template: "Reply removed from flagged post by staff"
+      text_body_template: |
+        Hello,
+
+        This is an automated message from %{site_name} to let you know that a [post](%{base_url}%{url}) you replied to was removed.
+
+        %{flag_reason}
+
+        This post was flagged by the community and a staff member opted to remove it.
+
+        `‍`` markdown
+        %{flagged_post_raw_content}
+        `‍``
+
+        For more details on the reason for removal, please review our [community guidelines](%{base_url}/guidelines).
+
     usage_tips:
       text_body_template: |
         For a few quick tips on getting started as a new user, [check out this blog post](https://blog.discourse.org/2016/12/discourse-new-user-tips-and-tricks/).
diff --git a/config/site_settings.yml b/config/site_settings.yml
index 9ea8cf7..5089815 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -828,6 +828,8 @@ posting:
   delete_removed_posts_after:
     client: true
     default: 24
+  notify_users_after_responses_deleted_on_flagged_post:
+    default: false
   traditional_markdown_linebreaks:
     client: true
     default: false
diff --git a/lib/post_destroyer.rb b/lib/post_destroyer.rb
index 9d09c7e..427fd6f 100644
--- a/lib/post_destroyer.rb
+++ b/lib/post_destroyer.rb
@@ -43,7 +43,10 @@ class PostDestroyer
     reply_ids = post.reply_ids(Guardian.new(performed_by), only_replies_to_single_post: false)
     replies = Post.where(id: reply_ids.map { |r| r[:id] })
     PostDestroyer.new(performed_by, post, reviewable: reviewable).destroy
-    replies.each { |reply| PostDestroyer.new(performed_by, reply, defer_flags: defer_reply_flags).destroy }
+
+    options = { defer_flags: defer_reply_flags }
+    options.merge!({ reviewable: reviewable, notify_responders: true, parent_post: post }) if SiteSetting.notify_users_after_responses_deleted_on_flagged_post
+    replies.each { |reply| PostDestroyer.new(performed_by, reply, options).destroy }
   end
 
   def initialize(user, post, opts = {})
@@ -179,7 +182,7 @@ class PostDestroyer
 
       DB.after_commit do
         if @opts[:reviewable]
-          notify_deletion(@opts[:reviewable])
+          notify_deletion(@opts[:reviewable], { notify_responders: @opts[:notify_responders], parent_post: @opts[:parent_post] })
         elsif reviewable = @post.reviewable_flag
           @opts[:defer_flags] ? ignore(reviewable) : agree(reviewable)
         end
@@ -315,21 +318,23 @@ class PostDestroyer
     reviewable.transition_to(:ignored, @user)
   end
 
-  def notify_deletion(reviewable)
+  def notify_deletion(reviewable, options = {})
     return if @post.user.blank?
 
     allowed_user = @user.human? && @user.staff?
     return unless allowed_user && rs = reviewable.reviewable_scores.order('created_at DESC').first
 
+    notify_responders = options[:notify_responders]
+
     Jobs.enqueue(
       :send_system_message,
       user_id: @post.user_id,
-      message_type: :flags_agreed_and_post_deleted,
+      message_type: notify_responders ? :flags_agreed_and_post_deleted_for_responders : :flags_agreed_and_post_deleted,
       message_options: {
-        flagged_post_raw_content: @post.raw,
-        url: @post.url,
+        flagged_post_raw_content: notify_responders ? options[:parent_post].raw : @post.raw,
+        url: notify_responders ? options[:parent_post].url : @post.url,
         flag_reason: I18n.t(
-          "flag_reasons.#{PostActionType.types[rs.reviewable_score_type]}",
+          "flag_reasons#{".responder" if notify_responders}.#{PostActionType.types[rs.reviewable_score_type]}",
           locale: SiteSetting.default_locale,
           base_path: Discourse.base_path
         )
diff --git a/spec/fabricators/reviewable_fabricator.rb b/spec/fabricators/reviewable_fabricator.rb
index bbf825d..b2f2612 100644
--- a/spec/fabricators/reviewable_fabricator.rb
+++ b/spec/fabricators/reviewable_fabricator.rb
@@ -57,6 +57,9 @@ Fabricator(:reviewable_flagged_post) do
   topic
   target_type 'Post'
   target { Fabricate(:post) }
+  reviewable_scores { |p| [
+    Fabricate.build(:reviewable_score, reviewable_id: p[:id]),
+  ]}
 end
 
 Fabricator(:reviewable_user) do
diff --git a/spec/fabricators/reviewable_score_fabricator.rb b/spec/fabricators/reviewable_score_fabricator.rb
new file mode 100644
index 0000000..81e781b
--- /dev/null
+++ b/spec/fabricators/reviewable_score_fabricator.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+Fabricator(:reviewable_score) do
+  reviewable { Fabricate(:reviewable) }
+  user { Fabricate(:user) }
+  reviewable_score_type { 4 }
+  status { 1 }
+  score { 11.0 }
+  reviewed_by { Fabricate(:user) }
+end
diff --git a/spec/models/reviewable_flagged_post_spec.rb b/spec/models/reviewable_flagged_post_spec.rb
index 8b36f82..f322024 100644
--- a/spec/models/reviewable_flagged_post_spec.rb
+++ b/spec/models/reviewable_flagged_post_spec.rb
@@ -283,16 +283,27 @@ RSpec.describe ReviewableFlaggedPost, type: :model do
   end
 
   describe "#perform_delete_and_agree_replies" do
-    it 'ignore flagged replies' do
-      flagged_post = Fabricate(:reviewable_flagged_post)
-      reply = create_reply(flagged_post.target)

[... diff too long, it was truncated ...]

GitHub sha: 8c7cc426b7064523925fae817ef9a5a138a63d50

This commit appears in #15049 which was approved by CvX. It was merged by janzenisaac.