diff --git a/app/assets/javascripts/admin/models/admin_user.js b/app/assets/javascripts/admin/models/admin_user.js
index 2e68f50a128..ab7aa34ffe0 100644
--- a/app/assets/javascripts/admin/models/admin_user.js
+++ b/app/assets/javascripts/admin/models/admin_user.js
@@ -67,6 +67,37 @@ Discourse.AdminUser = Discourse.User.extend({
return site.get('trust_levels').findProperty('id', this.get('trust_level'));
}.property('trust_level'),
+ setOriginalTrustLevel: function() {
+ this.set('originalTrustLevel', this.get('trust_level'));
+ },
+
+ trustLevels: function() {
+ var site = Discourse.Site.instance();
+ return site.get('trust_levels');
+ }.property('trust_level'),
+
+ dirty: function() {
+ return this.get('originalTrustLevel') !== parseInt(this.get('trustLevel.id'), 10);
+ }.property('originalTrustLevel', 'trustLevel.id'),
+
+ saveTrustLevel: function() {
+ Discourse.ajax("/admin/users/" + this.id + "/trust_level", {
+ type: 'PUT',
+ data: {level: this.get('trustLevel.id')}
+ }).then(function () {
+ // succeeded
+ window.location.reload();
+ }, function(e) {
+ // failure
+ var error = Em.String.i18n('admin.user.trust_level_change_failed', { error: "http: " + e.status + " - " + e.body });
+ bootbox.alert(error);
+ });
+ },
+
+ restoreTrustLevel: function() {
+ this.set('trustLevel.id', this.get('originalTrustLevel'));
+ },
+
isBanned: (function() {
return this.get('is_banned') === true;
}).property('is_banned'),
diff --git a/app/assets/javascripts/admin/routes/admin_user_route.js b/app/assets/javascripts/admin/routes/admin_user_route.js
index ab38ba41921..da0063e9999 100644
--- a/app/assets/javascripts/admin/routes/admin_user_route.js
+++ b/app/assets/javascripts/admin/routes/admin_user_route.js
@@ -16,6 +16,11 @@ Discourse.AdminUserRoute = Discourse.Route.extend(Discourse.ModelReady, {
return Discourse.AdminUser.find(Em.get(params, 'username').toLowerCase());
},
+ setupController: function(controller, model) {
+ controller.set('model', model);
+ model.setOriginalTrustLevel();
+ },
+
renderTemplate: function() {
this.render({into: 'admin/templates/admin'});
},
diff --git a/app/assets/javascripts/admin/templates/user.js.handlebars b/app/assets/javascripts/admin/templates/user.js.handlebars
index 2e953e7f369..60db63a606e 100644
--- a/app/assets/javascripts/admin/templates/user.js.handlebars
+++ b/app/assets/javascripts/admin/templates/user.js.handlebars
@@ -161,10 +161,22 @@
+
{{i18n admin.user.banned}}
{{isBanned}}
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 1c51293b656..6c1e1de5ca7 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -1,9 +1,10 @@
require_dependency 'user_destroyer'
require_dependency 'admin_user_index_query'
+require_dependency 'boost_trust_level'
class Admin::UsersController < Admin::AdminController
- before_filter :fetch_user, only: [:ban, :unban, :refresh_browsers, :revoke_admin, :grant_admin, :revoke_moderation, :grant_moderation, :approve, :activate, :deactivate, :block, :unblock]
+ before_filter :fetch_user, only: [:ban, :unban, :refresh_browsers, :revoke_admin, :grant_admin, :revoke_moderation, :grant_moderation, :approve, :activate, :deactivate, :block, :unblock, :trust_level]
def index
query = ::AdminUserIndexQuery.new(params)
@@ -69,6 +70,12 @@ class Admin::UsersController < Admin::AdminController
render_serialized(@user, AdminUserSerializer)
end
+ def trust_level
+ guardian.ensure_can_change_trust_level!(@user)
+ BoostTrustLevel.new(@user, params[:level]).save!
+ render_serialized(@user, AdminUserSerializer)
+ end
+
def approve
guardian.ensure_can_approve!(@user)
@user.approve(current_user)
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 960ef4ed45f..2b6ebb96f8e 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -1207,7 +1207,7 @@ en:
deactivate_explanation: "A deactivated user must re-validate their email."
banned_explanation: "A banned user can't log in."
block_explanation: "A blocked user can't post or start topics."
-
+ trust_level_change_failed: "There was a problem changing the user's trust level."
site_content:
none: "Choose a type of content to begin editing."
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 10e53bfa530..17b2630eedf 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -134,6 +134,8 @@ en:
title: "leader"
elder:
title: "elder"
+ change_failed_explanation: "You attempted to demote %{user_name} to '%{new_trust_level}'. However their trust level is already '%{current_trust_level}'. %{user_name} will remain at '%{current_trust_level}'"
+
rate_limiter:
too_many_requests: "You're doing that too often. Please wait %{time_left} before trying again."
diff --git a/config/routes.rb b/config/routes.rb
index 556ea294eb4..7cb1d650f51 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -49,6 +49,7 @@ Discourse::Application.routes.draw do
put 'deactivate'
put 'block'
put 'unblock'
+ put 'trust_level'
end
resources :impersonate, constraints: AdminConstraint.new
diff --git a/lib/boost_trust_level.rb b/lib/boost_trust_level.rb
new file mode 100644
index 00000000000..597f380866e
--- /dev/null
+++ b/lib/boost_trust_level.rb
@@ -0,0 +1,40 @@
+require_dependency 'promotion'
+
+class BoostTrustLevel
+
+ def initialize(user, level)
+ @user = user
+ @level = level.to_i
+ @promotion = Promotion.new(@user)
+ @trust_levels = TrustLevel.levels
+ end
+
+ def save!
+ if @level < @user.trust_level
+ demote!
+ else
+ @user.update_attributes!(trust_level: @level)
+ end
+ end
+
+ protected
+
+ def demote!
+ current_trust_level = @user.trust_level
+ @user.update_attributes!(trust_level: @level)
+ if @promotion.review
+ @user.update_attributes!(trust_level: current_trust_level)
+ raise Discourse::InvalidAccess.new, I18n.t('trust_levels.change_failed_explanation',
+ user_name: @user.name,
+ new_trust_level: trust_level_lookup(@level),
+ current_trust_level: trust_level_lookup(current_trust_level))
+ else
+ true
+ end
+ end
+
+ def trust_level_lookup(level)
+ @trust_levels.key(level).id2name
+ end
+
+end
diff --git a/lib/guardian.rb b/lib/guardian.rb
index ed49762376b..4a7589f875a 100644
--- a/lib/guardian.rb
+++ b/lib/guardian.rb
@@ -137,6 +137,10 @@ class Guardian
user && is_staff?
end
+ def can_change_trust_level?(user)
+ can_administer?(user)
+ end
+
def can_block_user?(user)
user && is_staff? && not(user.staff?)
end
diff --git a/spec/components/boost_trust_level_spec.rb b/spec/components/boost_trust_level_spec.rb
new file mode 100644
index 00000000000..26412079337
--- /dev/null
+++ b/spec/components/boost_trust_level_spec.rb
@@ -0,0 +1,48 @@
+require 'spec_helper'
+require 'boost_trust_level'
+
+describe BoostTrustLevel do
+
+ let(:user) { Fabricate(:user) }
+
+ it "should upgrade the trust level of a user" do
+ boostr = BoostTrustLevel.new(user, TrustLevel.levels[:basic])
+ boostr.save!.should be_true
+ user.trust_level.should == TrustLevel.levels[:basic]
+ end
+
+ describe "demotions" do
+ before { user.update_attributes(trust_level: TrustLevel.levels[:newuser]) }
+
+ context "for a user that has not done the requisite things to attain their trust level" do
+
+ before do
+ # scenario: admin mistakenly promotes user's trust level
+ user.update_attributes(trust_level: TrustLevel.levels[:basic])
+ end
+
+ it "should demote the user" do
+ boostr = BoostTrustLevel.new(user, TrustLevel.levels[:newuser])
+ boostr.save!.should be_true
+ user.trust_level.should == TrustLevel.levels[:newuser]
+ end
+ end
+
+ context "for a user that has done the requisite things to attain their trust level" do
+
+ before do
+ user.topics_entered = SiteSetting.basic_requires_topics_entered + 1
+ user.posts_read_count = SiteSetting.basic_requires_read_posts + 1
+ user.time_read = SiteSetting.basic_requires_time_spent_mins * 60
+ user.save!
+ user.update_attributes(trust_level: TrustLevel.levels[:basic])
+ end
+
+ it "should not demote the user" do
+ boostr = BoostTrustLevel.new(user, TrustLevel.levels[:newuser])
+ expect { boostr.save! }.to raise_error(Discourse::InvalidAccess, "You attempted to demote #{user.name} to 'newuser'. However their trust level is already 'basic'. #{user.name} will remain at 'basic'")
+ user.trust_level.should == TrustLevel.levels[:basic]
+ end
+ end
+ end
+end
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index 8467594137e..35fbaab5887 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -120,6 +120,39 @@ describe Admin::UsersController do
end
end
+ context '.trust_level' do
+ before do
+ @another_user = Fabricate(:coding_horror)
+ end
+
+ it "raises an error when the user doesn't have permission" do
+ Guardian.any_instance.expects(:can_change_trust_level?).with(@another_user).returns(false)
+ xhr :put, :trust_level, user_id: @another_user.id
+ response.should be_forbidden
+ end
+
+ it "returns a 404 if the username doesn't exist" do
+ xhr :put, :trust_level, user_id: 123123
+ response.should be_forbidden
+ end
+
+ it "upgrades the user's trust level" do
+ xhr :put, :trust_level, user_id: @another_user.id, level: 2
+ @another_user.reload
+ @another_user.trust_level.should == 2
+ end
+
+ it "raises an error when demoting a user below their current trust level" do
+ @another_user.topics_entered = SiteSetting.basic_requires_topics_entered + 1
+ @another_user.posts_read_count = SiteSetting.basic_requires_read_posts + 1
+ @another_user.time_read = SiteSetting.basic_requires_time_spent_mins * 60
+ @another_user.save!
+ @another_user.update_attributes(trust_level: TrustLevel.levels[:basic])
+ xhr :put, :trust_level, user_id: @another_user.id, level: TrustLevel.levels[:newuser]
+ response.should be_forbidden
+ end
+ end
+
describe '.revoke_moderation' do
before do
@moderator = Fabricate(:moderator)