From 075ed1ab53bef4f55c1dfc0334269db2a87bc4c0 Mon Sep 17 00:00:00 2001
From: Neil Lalonde <neillalonde@gmail.com>
Date: Tue, 2 Jul 2013 14:42:30 -0400
Subject: [PATCH] Refactor user blocking code; hide the Block button in admin

---
 .../admin/templates/user.js.handlebars        | 10 +-
 app/controllers/admin/users_controller.rb     |  4 +-
 app/services/spam_rules_enforcer.rb           | 16 +--
 app/services/user_blocker.rb                  | 36 +++++++
 config/locales/server.en.yml                  |  9 ++
 .../admin/users_controller_spec.rb            | 22 ++---
 spec/services/spam_rules_enforcer_spec.rb     | 42 ++------
 spec/services/user_blocker_spec.rb            | 99 +++++++++++++++++++
 8 files changed, 168 insertions(+), 70 deletions(-)
 create mode 100644 app/services/user_blocker.rb
 create mode 100644 spec/services/user_blocker_spec.rb

diff --git a/app/assets/javascripts/admin/templates/user.js.handlebars b/app/assets/javascripts/admin/templates/user.js.handlebars
index fbad6ba19..2e953e7f3 100644
--- a/app/assets/javascripts/admin/templates/user.js.handlebars
+++ b/app/assets/javascripts/admin/templates/user.js.handlebars
@@ -175,15 +175,16 @@
         {{i18n admin.user.unban}}
       </button>
       {{banDuration}}
+      {{i18n admin.user.banned_explanation}}
     {{else}}
       {{#if canBan}}
         <button class='btn btn-danger' {{action ban target="content"}}>
           <i class='icon icon-ban-circle'></i>
           {{i18n admin.user.ban}}
         </button>
+        {{i18n admin.user.banned_explanation}}
       {{/if}}
     {{/if}}
-    {{i18n admin.user.banned_explanation}}
     </div>
   </div>
   <div class='display-row'>
@@ -195,13 +196,8 @@
           <i class='icon icon-thumbs-up'></i>
           {{i18n admin.user.unblock}}
         </button>
-      {{else}}
-        <button class='btn btn-danger' {{action block target="content"}}>
-          <i class='icon icon-ban-circle'></i>
-          {{i18n admin.user.block}}
-        </button>
+        {{i18n admin.user.block_explanation}}
       {{/if}}
-      {{i18n admin.user.block_explanation}}
     </div>
   </div>
 </section>
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index c579ba8bc..1c51293b6 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -96,13 +96,13 @@ class Admin::UsersController < Admin::AdminController
 
   def block
     guardian.ensure_can_block_user! @user
-    SpamRulesEnforcer.punish! @user
+    UserBlocker.block(@user, current_user)
     render nothing: true
   end
 
   def unblock
     guardian.ensure_can_unblock_user! @user
-    SpamRulesEnforcer.clear @user
+    UserBlocker.unblock(@user, current_user)
     render nothing: true
   end
 
diff --git a/app/services/spam_rules_enforcer.rb b/app/services/spam_rules_enforcer.rb
index afda275c3..e48b7853b 100644
--- a/app/services/spam_rules_enforcer.rb
+++ b/app/services/spam_rules_enforcer.rb
@@ -17,10 +17,6 @@ class SpamRulesEnforcer
     SpamRulesEnforcer.new(user).punish_user
   end
 
-  def self.clear(user)
-    SpamRulesEnforcer.new(user).clear_user
-  end
-
 
   def initialize(user)
     @user = user
@@ -52,20 +48,10 @@ class SpamRulesEnforcer
 
   def punish_user
     Post.transaction do
-      Post.update_all(["hidden = true, hidden_reason_id = COALESCE(hidden_reason_id, ?)", Post.hidden_reasons[:new_user_spam_threshold_reached]], user_id: @user.id)
-      topic_ids = Post.where('user_id = ? and post_number = ?', @user.id, 1).pluck(:topic_id)
-      Topic.update_all({ visible: false }, id: topic_ids) unless topic_ids.empty?
-      SystemMessage.create(@user, :too_many_spam_flags)
+      UserBlocker.block(@user, nil, {message: :too_many_spam_flags})
       GroupMessage.create(Group[:moderators].name, :user_automatically_blocked, {user: @user, limit_once_per: false})
-      @user.blocked = true
-      @user.save
     end
   end
 
-  def clear_user
-    SystemMessage.create(@user, :unblocked)
-    @user.blocked = false
-    @user.save
-  end
 
 end
diff --git a/app/services/user_blocker.rb b/app/services/user_blocker.rb
new file mode 100644
index 000000000..5024b2f3f
--- /dev/null
+++ b/app/services/user_blocker.rb
@@ -0,0 +1,36 @@
+class UserBlocker
+
+  def initialize(user, by_user=nil, opts={})
+    @user, @by_user, @opts = user, by_user, opts
+  end
+
+  def self.block(user, by_user=nil, opts={})
+    UserBlocker.new(user, by_user, opts).block
+  end
+
+  def self.unblock(user, by_user=nil, opts={})
+    UserBlocker.new(user, by_user, opts).unblock
+  end
+
+  def block
+    hide_posts
+    @user.blocked = true
+    if @user.save
+      SystemMessage.create(@user, @opts[:message] || :blocked_by_staff)
+    end
+  end
+
+  def hide_posts
+    Post.update_all(["hidden = true, hidden_reason_id = COALESCE(hidden_reason_id, ?)", Post.hidden_reasons[:new_user_spam_threshold_reached]], user_id: @user.id)
+    topic_ids = Post.where('user_id = ? and post_number = ?', @user.id, 1).pluck(:topic_id)
+    Topic.update_all({ visible: false }, id: topic_ids) unless topic_ids.empty?
+  end
+
+  def unblock
+    @user.blocked = false
+    if @user.save
+      SystemMessage.create(@user, :unblocked)
+    end
+  end
+
+end
\ No newline at end of file
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 954053deb..83bfac2cd 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -872,6 +872,15 @@ en:
 
         For additional guidance, please refer to our [FAQ](%{base_url}/faq).
 
+    blocked_by_staff:
+      subject_template: "Account blocked"
+      text_body_template: |
+        Hello,
+
+        This is an automated message from %{site_name} to inform you that your account has been blocked by a staff member.
+
+        For additional guidance, please refer to our [FAQ](%{base_url}/faq).
+
     user_automatically_blocked:
       subject_template: "User %{username} was automatically blocked"
       text_body_template: |
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index 5e1f142ab..846759413 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -191,13 +191,13 @@ describe Admin::UsersController do
 
     context 'block' do
       before do
-        @user = Fabricate(:user)
+        @reg_user = Fabricate(:user)
       end
 
       it "raises an error when the user doesn't have permission" do
-        Guardian.any_instance.expects(:can_block_user?).with(@user).returns(false)
-        SpamRulesEnforcer.expects(:punish!).never
-        xhr :put, :block, user_id: @user.id
+        Guardian.any_instance.expects(:can_block_user?).with(@reg_user).returns(false)
+        UserBlocker.expects(:block).never
+        xhr :put, :block, user_id: @reg_user.id
         response.should be_forbidden
       end
 
@@ -207,19 +207,19 @@ describe Admin::UsersController do
       end
 
       it "punishes the user for spamming" do
-        SpamRulesEnforcer.expects(:punish!).with(@user)
-        xhr :put, :block, user_id: @user.id
+        UserBlocker.expects(:block).with(@reg_user, @user, anything)
+        xhr :put, :block, user_id: @reg_user.id
       end
     end
 
     context 'unblock' do
       before do
-        @user = Fabricate(:user)
+        @reg_user = Fabricate(:user)
       end
 
       it "raises an error when the user doesn't have permission" do
-        Guardian.any_instance.expects(:can_unblock_user?).with(@user).returns(false)
-        xhr :put, :unblock, user_id: @user.id
+        Guardian.any_instance.expects(:can_unblock_user?).with(@reg_user).returns(false)
+        xhr :put, :unblock, user_id: @reg_user.id
         response.should be_forbidden
       end
 
@@ -229,8 +229,8 @@ describe Admin::UsersController do
       end
 
       it "punishes the user for spamming" do
-        SpamRulesEnforcer.expects(:clear).with(@user)
-        xhr :put, :unblock, user_id: @user.id
+        UserBlocker.expects(:unblock).with(@reg_user, @user, anything)
+        xhr :put, :unblock, user_id: @reg_user.id
       end
     end
 
diff --git a/spec/services/spam_rules_enforcer_spec.rb b/spec/services/spam_rules_enforcer_spec.rb
index 00c1dc2f7..ff410cdad 100644
--- a/spec/services/spam_rules_enforcer_spec.rb
+++ b/spec/services/spam_rules_enforcer_spec.rb
@@ -2,6 +2,10 @@ require 'spec_helper'
 
 describe SpamRulesEnforcer do
 
+  before do
+    SystemMessage.stubs(:create)
+  end
+
   before do
     SiteSetting.stubs(:flags_required_to_hide_post).returns(0) # never
     SiteSetting.stubs(:num_flags_to_block_new_user).returns(2)
@@ -107,14 +111,9 @@ describe SpamRulesEnforcer do
       subject.stubs(:block?).returns(true)
     end
 
-    it "hides all the user's posts" do
+    it "blocks the user" do
+      UserBlocker.expects(:block).with(user, nil, has_entries(message: :too_many_spam_flags))
       subject.punish_user
-      expect(post.reload).to be_hidden
-    end
-
-    it "hides the topic if the post was the first post" do
-      subject.punish_user
-      expect(post.topic.reload).to_not be_visible
     end
 
     it 'prevents the user from making new posts' do
@@ -122,19 +121,13 @@ describe SpamRulesEnforcer do
       expect(Guardian.new(user).can_create_post?(nil)).to be_false
     end
 
-    it 'sends private messages to the user and to moderators' do
-      SystemMessage.expects(:create).with(user, anything, anything)
+    it 'sends private message to moderators' do
       moderator = Fabricate(:moderator)
       GroupMessage.expects(:create).with do |group, msg_type, params|
         group == Group[:moderators].name and msg_type == :user_automatically_blocked and params[:user].id == user.id
       end
       subject.punish_user
     end
-
-    it 'sets the blocked flag' do
-      subject.punish_user
-      expect(user.reload.blocked).to be_true
-    end
   end
 
   describe 'block?' do
@@ -220,25 +213,4 @@ describe SpamRulesEnforcer do
     end
   end
 
-  describe "clear_user" do
-    let!(:admin)  { Fabricate(:admin) } # needed for SystemMessage
-    let(:user)    { Fabricate(:user) }
-    subject       { SpamRulesEnforcer.new(user) }
-
-    it 'sets blocked flag to false' do
-      subject.clear_user
-      expect(user.reload).to_not be_blocked
-    end
-
-    it 'sends a system message' do
-      SystemMessage.expects(:create).with(user, anything, anything)
-      subject.clear_user
-    end
-
-    it 'allows user to make new posts' do
-      subject.clear_user
-      expect(Guardian.new(user).can_create_post?(nil)).to be_true
-    end
-  end
-
 end
diff --git a/spec/services/user_blocker_spec.rb b/spec/services/user_blocker_spec.rb
new file mode 100644
index 000000000..232b2b380
--- /dev/null
+++ b/spec/services/user_blocker_spec.rb
@@ -0,0 +1,99 @@
+require 'spec_helper'
+
+describe UserBlocker do
+
+  before do
+    SystemMessage.stubs(:create)
+  end
+
+  describe 'block' do
+    let(:user)           { stub_everything(save: true) }
+    let(:blocker)        { UserBlocker.new(user) }
+    subject(:block_user) { blocker.block }
+
+    it 'blocks the user' do
+      u = Fabricate(:user)
+      expect { UserBlocker.block(u) }.to change { u.reload.blocked? }
+    end
+
+    it 'hides posts' do
+      blocker.expects(:hide_posts)
+      block_user
+    end
+
+    context 'given a staff user argument' do
+      it 'sends the correct message to the blocked user' do
+        SystemMessage.unstub(:create)
+        SystemMessage.expects(:create).with(user, :blocked_by_staff).returns(true)
+        UserBlocker.block(user, Fabricate.build(:admin))
+      end
+
+      # TODO: it 'logs the action'
+    end
+
+    context 'not given a staff user argument' do
+      it 'sends a default message to the user' do
+        SystemMessage.unstub(:create)
+        SystemMessage.expects(:create).with(user, :blocked_by_staff).returns(true)
+        UserBlocker.block(user, Fabricate.build(:admin))
+      end
+    end
+
+    context 'given a message option' do
+      it 'sends that message to the user' do
+        SystemMessage.unstub(:create)
+        SystemMessage.expects(:create).with(user, :the_custom_message).returns(true)
+        UserBlocker.block(user, Fabricate.build(:admin), {message: :the_custom_message})
+      end
+    end
+
+    it "doesn't send a pm if save fails" do
+      user.stubs(:save).returns(false)
+      SystemMessage.unstub(:create)
+      SystemMessage.expects(:create).never
+      block_user
+    end
+  end
+
+  describe 'unblock' do
+    let(:user)             { stub_everything(save: true) }
+    subject(:unblock_user) { UserBlocker.unblock(user, Fabricate.build(:admin)) }
+
+    it 'unblocks the user' do
+      u = Fabricate(:user, blocked: true)
+      expect { UserBlocker.unblock(u) }.to change { u.reload.blocked? }
+    end
+
+    it 'sends a message to the user' do
+      SystemMessage.unstub(:create)
+      SystemMessage.expects(:create).with(user, :unblocked).returns(true)
+      unblock_user
+    end
+
+    it "doesn't send a pm if save fails" do
+      user.stubs(:save).returns(false)
+      SystemMessage.unstub(:create)
+      SystemMessage.expects(:create).never
+      unblock_user
+    end
+
+    # TODO: it 'logs the action'
+  end
+
+  describe 'hide_posts' do
+    let(:user)    { Fabricate(:user) }
+    let!(:post)   { Fabricate(:post, user: user) }
+    subject       { UserBlocker.new(user) }
+
+    it "hides all the user's posts" do
+      subject.block
+      expect(post.reload).to be_hidden
+    end
+
+    it "hides the topic if the post was the first post" do
+      subject.block
+      expect(post.topic.reload).to_not be_visible
+    end
+  end
+
+end