From 1cac9fa257331decc33f53cdff1b43fa9467e27a Mon Sep 17 00:00:00 2001
From: Robin Ward <robin.ward@gmail.com>
Date: Thu, 19 Dec 2013 13:45:55 -0500
Subject: [PATCH] New users can only post `newuser_max_replies_per_topic` times
 per topic.

---
 app/assets/stylesheets/desktop/compose.scss   |  6 +++++-
 app/models/user.rb                            |  5 +++++
 config/locales/server.en.yml                  | 10 ++++++++++
 config/site_settings.yml                      |  1 +
 lib/composer_messages_finder.rb               |  7 +++++++
 lib/validators/post_validator.rb              |  7 +++++++
 .../composer_messages_finder_spec.rb          | 19 +++++++++++++++++++
 .../validators/post_validator_spec.rb         | 14 ++++++++++++++
 8 files changed, 68 insertions(+), 1 deletion(-)

diff --git a/app/assets/stylesheets/desktop/compose.scss b/app/assets/stylesheets/desktop/compose.scss
index c4b46c501..30a63b3fa 100644
--- a/app/assets/stylesheets/desktop/compose.scss
+++ b/app/assets/stylesheets/desktop/compose.scss
@@ -18,8 +18,12 @@
 
   @include box-shadow(3px 3px 3px rgba($black, 0.14));
 
+  h3 {
+    margin-bottom: 10px;
+  }
+
   p {
-    margin: 0 0 10px 0;
+    margin-bottom: 10px;
   }
 
   a.close {
diff --git a/app/models/user.rb b/app/models/user.rb
index f175dfd03..4e4831c35 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -143,6 +143,7 @@ class User < ActiveRecord::Base
     where(username_lower: username.downcase).first
   end
 
+
   def enqueue_welcome_message(message_type)
     return unless SiteSetting.send_welcome_message?
     Jobs.enqueue(:send_system_message, user_id: id, message_type: message_type)
@@ -340,6 +341,10 @@ class User < ActiveRecord::Base
     topics_allowed.where(archetype: Archetype.private_message).count
   end
 
+  def posted_too_much_in_topic?(topic_id)
+    trust_level == TrustLevel.levels[:newuser] && (Post.where(topic_id: topic_id, user_id: id).count >= SiteSetting.newuser_max_replies_per_topic)
+  end
+
   def bio_excerpt
     excerpt = PrettyText.excerpt(bio_cooked, 350)
     return excerpt if excerpt.blank? || has_trust_level?(:basic)
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index de9d54678..1c54ddbf0 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -27,6 +27,8 @@ en:
   site_under_maintenance: 'Site is currently undergoing maintenance.'
   operation_already_running: "An %{operation} is currently running. Can't start a new %{operation} job right now."
 
+  too_many_replies: "Sorry you can't reply any more times in that topic."
+
   too_many_mentions:
     zero: "Sorry, you can't mention other users."
     one: "Sorry, you can only mention one other user in a post."
@@ -132,6 +134,13 @@ en:
 
       Are you sure you're providing adequate time for other people to share their points of view, too?
 
+    too_many_replies: |
+      ### You've replied too many times to this topic
+
+      Sorry, but new users are limited to %{newuser_max_replies_per_topic} replies in a topic.
+
+      You should consider editing one of your previous replies instead of a new reply.
+
   activerecord:
     attributes:
       category:
@@ -667,6 +676,7 @@ en:
     newuser_max_images: "How many images a new user can add to a post"
     newuser_max_attachments: "How many attachments a new user can add to a post"
     newuser_max_mentions_per_post: "Maximum number of @name notifications a new user can use in a post"
+    newuser_max_replies_per_topic: "Maximum number of replies a new user can make in a single topic"
     max_mentions_per_post: "Maximum number of @name notifications you can use in a post"
 
     create_thumbnails: "Create thumbnails for lightboxed images"
diff --git a/config/site_settings.yml b/config/site_settings.yml
index 4b1bbe40c..286d6e575 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -177,6 +177,7 @@ posting:
     default: true
   post_undo_action_window_mins: 10
   max_mentions_per_post: 10
+  newuser_max_replies_per_topic: 3
   newuser_max_mentions_per_post: 2
   onebox_max_chars: 5000
   title_min_entropy: 10
diff --git a/lib/composer_messages_finder.rb b/lib/composer_messages_finder.rb
index c39731d82..c7f27bcba 100644
--- a/lib/composer_messages_finder.rb
+++ b/lib/composer_messages_finder.rb
@@ -7,6 +7,7 @@ class ComposerMessagesFinder
 
   def find
     check_education_message ||
+    check_new_user_many_replies ||
     check_avatar_notification ||
     check_sequential_replies ||
     check_dominating_topic
@@ -32,6 +33,12 @@ class ComposerMessagesFinder
     nil
   end
 
+  # New users have a limited number of replies in a topic
+  def check_new_user_many_replies
+    return unless replying? && @user.posted_too_much_in_topic?(@details[:topic_id])
+    {templateName: 'composer/education', body: PrettyText.cook(I18n.t('education.too_many_replies', newuser_max_replies_per_topic: SiteSetting.newuser_max_replies_per_topic)) }
+  end
+
   # Should a user be contacted to update their avatar?
   def check_avatar_notification
 
diff --git a/lib/validators/post_validator.rb b/lib/validators/post_validator.rb
index 124d55058..5c5dab1ef 100644
--- a/lib/validators/post_validator.rb
+++ b/lib/validators/post_validator.rb
@@ -5,6 +5,7 @@ class Validators::PostValidator < ActiveModel::Validator
     presence(record)
     stripped_length(record)
     raw_quality(record)
+    max_posts_validator(record)
     max_mention_validator(record)
     max_images_validator(record)
     max_attachments_validator(record)
@@ -40,6 +41,12 @@ class Validators::PostValidator < ActiveModel::Validator
     end
   end
 
+  def max_posts_validator(post)
+    if post.acting_user.present? && post.acting_user.posted_too_much_in_topic?(post.topic_id)
+      post.errors.add(:base, I18n.t(:too_many_replies))
+    end
+  end
+
   # Ensure new users can not put too many images in a post
   def max_images_validator(post)
     add_error_if_count_exceeded(post, :too_many_images, post.image_count, SiteSetting.newuser_max_images) unless acting_user_is_trusted?(post)
diff --git a/spec/components/composer_messages_finder_spec.rb b/spec/components/composer_messages_finder_spec.rb
index df26e9be6..f211157a1 100644
--- a/spec/components/composer_messages_finder_spec.rb
+++ b/spec/components/composer_messages_finder_spec.rb
@@ -10,6 +10,7 @@ describe ComposerMessagesFinder do
 
     it "calls all the message finders" do
       finder.expects(:check_education_message).once
+      finder.expects(:check_new_user_many_replies).once
       finder.expects(:check_avatar_notification).once
       finder.expects(:check_sequential_replies).once
       finder.expects(:check_dominating_topic).once
@@ -56,6 +57,24 @@ describe ComposerMessagesFinder do
         finder.check_education_message.should be_blank
       end
     end
+  end
+
+  context '.check_new_user_many_replies' do
+    let(:user) { Fabricate.build(:user) }
+
+    context 'replying' do
+      let(:finder) { ComposerMessagesFinder.new(user, composerAction: 'reply') }
+
+      it "has no message when `posted_too_much_in_topic?` is false" do
+        user.expects(:posted_too_much_in_topic?).returns(false)
+        finder.check_new_user_many_replies.should be_blank
+      end
+
+      it "has a message when a user has posted too much" do
+        user.expects(:posted_too_much_in_topic?).returns(true)
+        finder.check_new_user_many_replies.should be_present
+      end
+    end
 
   end
 
diff --git a/spec/components/validators/post_validator_spec.rb b/spec/components/validators/post_validator_spec.rb
index 76865503d..e1a5ed725 100644
--- a/spec/components/validators/post_validator_spec.rb
+++ b/spec/components/validators/post_validator_spec.rb
@@ -24,6 +24,20 @@ describe Validators::PostValidator do
     end
   end
 
+  context "too_many_posts" do
+    it "should be invalid when the user has posted too much" do
+      post.user.expects(:posted_too_much_in_topic?).returns(true)
+      validator.max_posts_validator(post)
+      expect(post.errors.count).to be > 0
+    end
+
+    it "should be valid when the user hasn't posted too much" do
+      post.user.expects(:posted_too_much_in_topic?).returns(false)
+      validator.max_posts_validator(post)
+      expect(post.errors.count).to be(0)
+    end
+  end
+
   context "invalid post" do
     it "should be invalid" do
       validator.validate(post)