FEATURE: per-category approval settings (#5778)

- disallow moving topics to a category that requires topic approval
This commit is contained in:
Kyle Zhao 2018-07-12 22:51:08 -04:00 committed by Sam
parent db67c87916
commit 2901691e87
15 changed files with 187 additions and 7 deletions

View File

@ -124,7 +124,8 @@ const DiscoveryCategoriesRoute = Discourse.Route.extend(OpenComposer, {
group_permissions: [{ group_name: everyoneName, permission_type: 1 }], group_permissions: [{ group_name: everyoneName, permission_type: 1 }],
available_groups: groups.map(g => g.name), available_groups: groups.map(g => g.name),
allow_badges: true, allow_badges: true,
topic_featured_link_allowed: true topic_featured_link_allowed: true,
custom_fields: {}
}); });
showModal("edit-category", { model }); showModal("edit-category", { model });

View File

@ -161,4 +161,18 @@
</section> </section>
{{/if}} {{/if}}
<section class="field">
<label>
{{input type="checkbox" checked=category.custom_fields.require_topic_approval}}
{{i18n 'category.require_topic_approval'}}
</label>
</section>
<section class="field">
<label>
{{input type="checkbox" checked=category.custom_fields.require_reply_approval}}
{{i18n 'category.require_reply_approval'}}
</label>
</section>
{{plugin-outlet name="category-custom-settings" args=(hash category=category)}} {{plugin-outlet name="category-custom-settings" args=(hash category=category)}}

View File

@ -204,7 +204,7 @@ class PostsController < ApplicationController
if changes[:category_id] && changes[:category_id].to_i != post.topic.category_id.to_i if changes[:category_id] && changes[:category_id].to_i != post.topic.category_id.to_i
category = Category.find_by(id: changes[:category_id]) category = Category.find_by(id: changes[:category_id])
if category || (changes[:category_id].to_i == 0) if category || (changes[:category_id].to_i == 0)
guardian.ensure_can_create_topic_on_category!(category) guardian.ensure_can_move_topic_to_category!(category)
else else
return render_json_error(I18n.t('category.errors.not_found')) return render_json_error(I18n.t('category.errors.not_found'))
end end

View File

@ -259,7 +259,7 @@ class TopicsController < ApplicationController
if params[:category_id] && (params[:category_id].to_i != topic.category_id.to_i) if params[:category_id] && (params[:category_id].to_i != topic.category_id.to_i)
category = Category.find_by(id: params[:category_id]) category = Category.find_by(id: params[:category_id])
if category || (params[:category_id].to_i == 0) if category || (params[:category_id].to_i == 0)
guardian.ensure_can_create_topic_on_category!(category) guardian.ensure_can_move_topic_to_category!(category)
else else
return render_json_error(I18n.t('category.errors.not_found')) return render_json_error(I18n.t('category.errors.not_found'))
end end

View File

@ -7,6 +7,12 @@ class Category < ActiveRecord::Base
include CategoryHashtag include CategoryHashtag
include AnonCacheInvalidator include AnonCacheInvalidator
REQUIRE_TOPIC_APPROVAL = 'require_topic_approval'
REQUIRE_REPLY_APPROVAL = 'require_reply_approval'
register_custom_field_type(REQUIRE_TOPIC_APPROVAL, :boolean)
register_custom_field_type(REQUIRE_REPLY_APPROVAL, :boolean)
belongs_to :topic, dependent: :destroy belongs_to :topic, dependent: :destroy
belongs_to :topic_only_relative_url, belongs_to :topic_only_relative_url,
-> { select "id, title, slug" }, -> { select "id, title, slug" },
@ -351,6 +357,14 @@ class Category < ActiveRecord::Base
[read_restricted, mapped] [read_restricted, mapped]
end end
def require_topic_approval?
custom_fields[REQUIRE_TOPIC_APPROVAL]
end
def require_reply_approval?
custom_fields[REQUIRE_REPLY_APPROVAL]
end
def allowed_tags=(tag_names_arg) def allowed_tags=(tag_names_arg)
DiscourseTagging.add_or_create_tags_by_name(self, tag_names_arg, unlimited: true) DiscourseTagging.add_or_create_tags_by_name(self, tag_names_arg, unlimited: true)
end end

View File

@ -2280,6 +2280,8 @@ en:
allow_badges_label: "Allow badges to be awarded in this category" allow_badges_label: "Allow badges to be awarded in this category"
edit_permissions: "Edit Permissions" edit_permissions: "Edit Permissions"
add_permission: "Add Permission" add_permission: "Add Permission"
require_topic_approval: "Require moderator approval of all new topics"
require_reply_approval: "Require moderator approval of all new replies"
this_year: "this year" this_year: "this year"
position: "position" position: "position"
default_position: "Default Position" default_position: "Default Position"

View File

@ -34,6 +34,12 @@ module TopicGuardian
(!category || Category.topic_create_allowed(self).where(id: category_id).count == 1) (!category || Category.topic_create_allowed(self).where(id: category_id).count == 1)
end end
def can_move_topic_to_category?(category)
category = Category === category ? category : Category.find(category || SiteSetting.uncategorized_category_id)
is_staff? || (can_create_topic_on_category?(category) && !category.require_topic_approval?)
end
def can_create_post_on_topic?(topic) def can_create_post_on_topic?(topic)
# No users can create posts on deleted topics # No users can create posts on deleted topics
return false if topic.blank? return false if topic.blank?

View File

@ -82,7 +82,20 @@ class NewPostManager
is_fast_typer?(manager) || is_fast_typer?(manager) ||
matches_auto_silence_regex?(manager) || matches_auto_silence_regex?(manager) ||
WordWatcher.new("#{manager.args[:title]} #{manager.args[:raw]}").requires_approval? || WordWatcher.new("#{manager.args[:title]} #{manager.args[:raw]}").requires_approval? ||
(SiteSetting.approve_unless_staged && user.staged) (SiteSetting.approve_unless_staged && user.staged) ||
post_needs_approval_in_its_category?(manager)
end
def self.post_needs_approval_in_its_category?(manager)
if manager.args[:topic_id].present?
cat = Category.joins(:topics).find_by(topics: { id: manager.args[:topic_id] })
return false unless cat
cat.require_reply_approval?
elsif manager.args[:category].present?
Category.find(manager.args[:category]).require_topic_approval?
else
false
end
end end
def self.default_handler(manager) def self.default_handler(manager)

View File

@ -69,7 +69,7 @@ class PostRevisor
end end
track_topic_field(:category_id) do |tc, category_id| track_topic_field(:category_id) do |tc, category_id|
if category_id == 0 || tc.guardian.can_create_topic_on_category?(category_id) if category_id == 0 || tc.guardian.can_move_topic_to_category?(category_id)
tc.record_change('category_id', tc.topic.category_id, category_id) tc.record_change('category_id', tc.topic.category_id, category_id)
tc.check_result(tc.topic.change_category_to_id(category_id)) tc.check_result(tc.topic.change_category_to_id(category_id))
end end

View File

@ -281,4 +281,51 @@ describe NewPostManager do
end end
end end
context 'when posting in the category requires approval' do
let(:user) { Fabricate(:user) }
let(:category) { Fabricate(:category) }
context 'when new topics require approval' do
before do
category.custom_fields[Category::REQUIRE_TOPIC_APPROVAL] = true
category.save
end
it 'enqueues new topics' do
manager = NewPostManager.new(
user,
raw: 'this is a new topic',
title: "Let's start a new topic!",
category: category.id
)
expect(manager.perform.action).to eq(:enqueued)
end
end
context 'when new posts require approval' do
let(:topic) { Fabricate(:topic, category: category) }
before do
category.custom_fields[Category::REQUIRE_REPLY_APPROVAL] = true
category.save
end
it 'enqueues new posts' do
manager = NewPostManager.new(user, raw: 'this is a new post', topic_id: topic.id)
expect(manager.perform.action).to eq(:enqueued)
end
it "doesn't blow up with invalid topic_id" do
expect do
manager = NewPostManager.new(
user,
raw: 'this is a new topic',
topic_id: 97546
)
expect(manager.perform.action).to eq(:create_post)
end.not_to raise_error
end
end
end
end end

View File

@ -62,6 +62,23 @@ describe PostRevisor do
expect(post.topic.category_id).to eq(category.id) expect(post.topic.category_id).to eq(category.id)
end end
it 'does not revise category when the destination category requires topic approval' do
new_category = Fabricate(:category)
new_category.custom_fields[Category::REQUIRE_TOPIC_APPROVAL] = true
new_category.save!
post = create_post
old_category_id = post.topic.category_id
post.revise(post.user, category_id: new_category.id)
expect(post.reload.topic.category_id).to eq(old_category_id)
new_category.custom_fields[Category::REQUIRE_TOPIC_APPROVAL] = false
new_category.save!
post.revise(post.user, category_id: new_category.id)
expect(post.reload.topic.category_id).to eq(new_category.id)
end
end end
context 'revise wiki' do context 'revise wiki' do

View File

@ -663,4 +663,25 @@ describe Category do
end end
describe 'require topic/post approval' do
let(:category) { Fabricate(:category) }
describe '#require_topic_approval?' do
before do
category.custom_fields[Category::REQUIRE_TOPIC_APPROVAL] = true
category.save
end
it { expect(category.reload.require_topic_approval?).to eq(true) }
end
describe '#require_reply_approval?' do
before do
category.custom_fields[Category::REQUIRE_REPLY_APPROVAL] = true
category.save
end
it { expect(category.reload.require_reply_approval?).to eq(true) }
end
end
end end

View File

@ -296,6 +296,26 @@ describe CategoriesController do
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(UserHistory.count).to eq(5) # 2 + 3 (bootstrap mode) expect(UserHistory.count).to eq(5) # 2 + 3 (bootstrap mode)
end end
it 'updates per-category approval settings correctly' do
category.custom_fields[Category::REQUIRE_TOPIC_APPROVAL] = false
category.custom_fields[Category::REQUIRE_REPLY_APPROVAL] = false
category.save!
put "/categories/#{category.id}.json", params: {
name: category.name,
color: category.color,
text_color: category.text_color,
custom_fields: {
require_reply_approval: true,
require_topic_approval: true,
}
}
category.reload
expect(category.require_topic_approval?).to eq(true)
expect(category.require_reply_approval?).to eq(true)
end
end end
end end
end end

View File

@ -340,6 +340,20 @@ describe PostsController do
expect(response.status).not_to eq(200) expect(response.status).not_to eq(200)
expect(post.topic.category_id).not_to eq(category.id) expect(post.topic.category_id).not_to eq(category.id)
end end
it 'can not move to a category that requires topic approval' do
post = create_post
sign_in(post.user)
category = Fabricate(:category)
category.custom_fields[Category::REQUIRE_TOPIC_APPROVAL] = true
category.save!
put "/posts/#{post.id}.json", params: { post: { category_id: category.id, raw: "this is a test edit to post" } }
expect(response.status).to eq(403)
expect(post.topic.reload.category_id).not_to eq(category.id)
end
end end
describe '#bookmark' do describe '#bookmark' do

View File

@ -671,8 +671,19 @@ RSpec.describe TopicsController do
put "/t/#{topic.id}.json", params: { category_id: category.id } put "/t/#{topic.id}.json", params: { category_id: category.id }
expect(response.status).not_to eq(200) expect(response.status).to eq(403)
expect(topic.category_id).not_to eq(category.id) expect(topic.reload.category_id).not_to eq(category.id)
end
it 'can not move to a category that requires topic approval' do
category = Fabricate(:category)
category.custom_fields[Category::REQUIRE_TOPIC_APPROVAL] = true
category.save!
put "/t/#{topic.id}.json", params: { category_id: category.id }
expect(response.status).to eq(403)
expect(topic.reload.category_id).not_to eq(category.id)
end end
describe 'without permission' do describe 'without permission' do