mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 15:16:08 +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 }],
|
||||
available_groups: groups.map(g => g.name),
|
||||
allow_badges: true,
|
||||
topic_featured_link_allowed: true
|
||||
topic_featured_link_allowed: true,
|
||||
custom_fields: {}
|
||||
});
|
||||
|
||||
showModal("edit-category", { model });
|
||||
|
|
|
@ -161,4 +161,18 @@
|
|||
</section>
|
||||
{{/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)}}
|
||||
|
|
|
@ -204,7 +204,7 @@ class PostsController < ApplicationController
|
|||
if changes[:category_id] && changes[:category_id].to_i != post.topic.category_id.to_i
|
||||
category = Category.find_by(id: changes[:category_id])
|
||||
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
|
||||
return render_json_error(I18n.t('category.errors.not_found'))
|
||||
end
|
||||
|
|
|
@ -259,7 +259,7 @@ class TopicsController < ApplicationController
|
|||
if params[:category_id] && (params[:category_id].to_i != topic.category_id.to_i)
|
||||
category = Category.find_by(id: params[:category_id])
|
||||
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
|
||||
return render_json_error(I18n.t('category.errors.not_found'))
|
||||
end
|
||||
|
|
|
@ -7,6 +7,12 @@ class Category < ActiveRecord::Base
|
|||
include CategoryHashtag
|
||||
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_only_relative_url,
|
||||
-> { select "id, title, slug" },
|
||||
|
@ -351,6 +357,14 @@ class Category < ActiveRecord::Base
|
|||
[read_restricted, mapped]
|
||||
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)
|
||||
DiscourseTagging.add_or_create_tags_by_name(self, tag_names_arg, unlimited: true)
|
||||
end
|
||||
|
|
|
@ -2280,6 +2280,8 @@ en:
|
|||
allow_badges_label: "Allow badges to be awarded in this category"
|
||||
edit_permissions: "Edit Permissions"
|
||||
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"
|
||||
position: "position"
|
||||
default_position: "Default Position"
|
||||
|
|
|
@ -34,6 +34,12 @@ module TopicGuardian
|
|||
(!category || Category.topic_create_allowed(self).where(id: category_id).count == 1)
|
||||
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)
|
||||
# No users can create posts on deleted topics
|
||||
return false if topic.blank?
|
||||
|
|
|
@ -82,7 +82,20 @@ class NewPostManager
|
|||
is_fast_typer?(manager) ||
|
||||
matches_auto_silence_regex?(manager) ||
|
||||
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
|
||||
|
||||
def self.default_handler(manager)
|
||||
|
|
|
@ -69,7 +69,7 @@ class PostRevisor
|
|||
end
|
||||
|
||||
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.check_result(tc.topic.change_category_to_id(category_id))
|
||||
end
|
||||
|
|
|
@ -281,4 +281,51 @@ describe NewPostManager do
|
|||
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
|
||||
|
|
|
@ -62,6 +62,23 @@ describe PostRevisor do
|
|||
expect(post.topic.category_id).to eq(category.id)
|
||||
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
|
||||
|
||||
context 'revise wiki' do
|
||||
|
|
|
@ -663,4 +663,25 @@ describe Category do
|
|||
|
||||
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
|
||||
|
|
|
@ -296,6 +296,26 @@ describe CategoriesController do
|
|||
expect(response.status).to eq(200)
|
||||
expect(UserHistory.count).to eq(5) # 2 + 3 (bootstrap mode)
|
||||
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
|
||||
|
|
|
@ -340,6 +340,20 @@ describe PostsController do
|
|||
expect(response.status).not_to eq(200)
|
||||
expect(post.topic.category_id).not_to eq(category.id)
|
||||
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
|
||||
|
||||
describe '#bookmark' do
|
||||
|
|
|
@ -671,8 +671,19 @@ RSpec.describe TopicsController do
|
|||
|
||||
put "/t/#{topic.id}.json", params: { category_id: category.id }
|
||||
|
||||
expect(response.status).not_to eq(200)
|
||||
expect(topic.category_id).not_to eq(category.id)
|
||||
expect(response.status).to eq(403)
|
||||
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
|
||||
|
||||
describe 'without permission' do
|
||||
|
|
Loading…
Reference in New Issue
Block a user