From ecafbb0a63fa0a9903ce22124b37ce3cf5077608 Mon Sep 17 00:00:00 2001
From: Robin Ward <robin.ward@gmail.com>
Date: Mon, 27 Apr 2015 15:06:20 -0400
Subject: [PATCH] Can delete users via the moderation queue

---
 .../discourse/controllers/queued-post.js.es6  | 18 +++++++++++++--
 .../discourse/models/result-set.js.es6        |  4 ++--
 .../discourse/templates/queued-posts.hbs      |  6 ++++-
 app/controllers/queued_posts_controller.rb    | 20 +++++++++++++++++
 config/locales/client.en.yml                  |  2 ++
 config/locales/server.en.yml                  |  3 +++
 .../acceptance/queued-posts-test.js.es6       | 22 +++++++++++++++++++
 7 files changed, 70 insertions(+), 5 deletions(-)

diff --git a/app/assets/javascripts/discourse/controllers/queued-post.js.es6 b/app/assets/javascripts/discourse/controllers/queued-post.js.es6
index 47ac01a0063..100b0b15866 100644
--- a/app/assets/javascripts/discourse/controllers/queued-post.js.es6
+++ b/app/assets/javascripts/discourse/controllers/queued-post.js.es6
@@ -1,10 +1,16 @@
 import BufferedContent from 'discourse/mixins/buffered-content';
 import { popupAjaxError } from 'discourse/lib/ajax-error';
 
-function updateState(state) {
+function updateState(state, opts) {
+  opts = opts || {};
+
   return function() {
     const post = this.get('post');
-    post.update({ state }).then(() => {
+    const args = { state };
+
+    if (opts.deleteUser) { args.delete_user = true; }
+
+    post.update(args).then(() => {
       this.get('controllers.queued-posts.model').removeObject(post);
     }).catch(popupAjaxError);
   };
@@ -17,10 +23,18 @@ export default Ember.Controller.extend(BufferedContent, {
 
   editing: Discourse.computed.propertyEqual('model', 'currentlyEditing'),
 
+  _confirmDelete: updateState('rejected', {deleteUser: true}),
+
   actions: {
     approve: updateState('approved'),
     reject: updateState('rejected'),
 
+    deleteUser() {
+      bootbox.confirm(I18n.t('queue.delete_prompt', {username: this.get('model.user.username')}), (confirmed) => {
+        if (confirmed) { this._confirmDelete(); }
+      });
+    },
+
     edit() {
       // This is stupid but pagedown cannot be on the screen twice or it will break
       this.set('currentlyEditing', null);
diff --git a/app/assets/javascripts/discourse/models/result-set.js.es6 b/app/assets/javascripts/discourse/models/result-set.js.es6
index bb0d54b2e8d..cda174b609d 100644
--- a/app/assets/javascripts/discourse/models/result-set.js.es6
+++ b/app/assets/javascripts/discourse/models/result-set.js.es6
@@ -13,7 +13,7 @@ export default Ember.ArrayProxy.extend({
       this.set('loadingMore', true);
 
       const self = this;
-      return this.store.appendResults(this, this.get('__type'), loadMoreUrl).then(function() {
+      return this.store.appendResults(this, this.get('__type'), loadMoreUrl).finally(function() {
         self.set('loadingMore', false);
       });
     }
@@ -29,7 +29,7 @@ export default Ember.ArrayProxy.extend({
 
     const self = this;
     this.set('refreshing', true);
-    return this.store.refreshResults(this, this.get('__type'), refreshUrl).then(function() {
+    return this.store.refreshResults(this, this.get('__type'), refreshUrl).finally(function() {
       self.set('refreshing', false);
     });
 
diff --git a/app/assets/javascripts/discourse/templates/queued-posts.hbs b/app/assets/javascripts/discourse/templates/queued-posts.hbs
index 3c9e0b04779..9b16c088b87 100644
--- a/app/assets/javascripts/discourse/templates/queued-posts.hbs
+++ b/app/assets/javascripts/discourse/templates/queued-posts.hbs
@@ -50,7 +50,6 @@
                          icon="times"
                          disabled=ctrl.post.isSaving
                          class="btn-danger cancel"}}
-
             {{else}}
               {{d-button action="approve"
                          disabled=ctrl.post.isSaving
@@ -62,6 +61,11 @@
                          label="queue.reject"
                          icon="times"
                          class="btn-danger reject"}}
+              {{d-button action="deleteUser"
+                         disabled=ctrl.post.isSaving
+                         label="queue.delete_user"
+                         icon="trash"
+                         class="btn-danger delete-user"}}
               {{d-button action="edit"
                          disabled=ctrl.post.isSaving
                          label="queue.edit"
diff --git a/app/controllers/queued_posts_controller.rb b/app/controllers/queued_posts_controller.rb
index 8ca61a9620a..bfa1391213c 100644
--- a/app/controllers/queued_posts_controller.rb
+++ b/app/controllers/queued_posts_controller.rb
@@ -29,9 +29,29 @@ class QueuedPostsController < ApplicationController
       qp.approve!(current_user)
     elsif state == 'rejected'
       qp.reject!(current_user)
+      if params[:queued_post][:delete_user] == 'true' && guardian.can_delete_user?(qp.user)
+        UserDestroyer.new(current_user).destroy(qp.user, user_deletion_opts)
+      end
     end
 
     render_serialized(qp, QueuedPostSerializer, root: :queued_posts)
   end
 
+
+  private
+
+    def user_deletion_opts
+      base = {
+        context:           I18n.t('queue.delete_reason', {performed_by: current_user.username}),
+        delete_posts:      true,
+        delete_as_spammer: true
+      }
+
+      if Rails.env.production? && ENV["Staging"].nil?
+        base.merge!({block_email: true, block_ip: true})
+      end
+
+      base
+    end
+
 end
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 3dc1163713b..e0ab53a68f4 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -231,11 +231,13 @@ en:
       topic: "Topic:"
       approve: 'Approve'
       reject: 'Reject'
+      delete_user: 'Delete User'
       title: "Needs Approval"
       none: "There are no posts to review."
       edit: "Edit"
       cancel: "Cancel"
       confirm: "Save Changes"
+      delete_prompt: "Are you sure you want to delete <b>%{username}</b>? This will remove all of their posts and block their email and ip address."
 
       approval:
         title: "Post Needs Approval"
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 36960de7961..4f58d3cd0c1 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -177,6 +177,9 @@ en:
 
   excerpt_image: "image"
 
+  queue:
+    delete_reason: "Deleted via post moderation queue"
+
   groups:
     errors:
       can_not_modify_automatic: "You can not modify an automatic group"
diff --git a/test/javascripts/acceptance/queued-posts-test.js.es6 b/test/javascripts/acceptance/queued-posts-test.js.es6
index de358c92a26..c1a755503a1 100644
--- a/test/javascripts/acceptance/queued-posts-test.js.es6
+++ b/test/javascripts/acceptance/queued-posts-test.js.es6
@@ -20,6 +20,28 @@ test("reject a post", () => {
   });
 });
 
+test("delete user", () => {
+  visit("/queued-posts");
+
+  click('.queued-post:eq(0) button.delete-user');
+  andThen(() => {
+    ok(exists('.bootbox.modal'), 'it pops up a confirmation dialog');
+  });
+
+  click('.modal-footer a:eq(1)');
+  andThen(() => {
+    ok(!exists('.bootbox.modal'), 'it dismisses the modal');
+    ok(exists('.queued-post'), "it doesn't remove the post");
+  });
+
+  click('.queued-post:eq(0) button.delete-user');
+  click('.modal-footer a:eq(0)');
+  andThen(() => {
+    ok(!exists('.bootbox.modal'), 'it dismisses the modal');
+    ok(!exists('.queued-post'), "it removes the post");
+  });
+});
+
 test("edit a post - cancel", () => {
   visit("/queued-posts");