mirror of
https://github.com/discourse/discourse.git
synced 2024-11-23 03:16:41 +08:00
FEATURE: per-category approval settings (#5778)
- disallow moving topics to a category that requires topic approval
This commit is contained in:
parent
db67c87916
commit
2901691e87
|
@ -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 });
|
||||||
|
|
|
@ -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)}}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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?
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user