Add post_edit_time_limit site setting to limit the how long a post can be edited and deleted by the author. Default is 1 year.
This commit is contained in:
parent
e750ea010f
commit
259295d865
|
@ -459,7 +459,17 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
|
|||
}
|
||||
]);
|
||||
} else {
|
||||
post.destroy(user);
|
||||
post.destroy(user).then(null, function(e) {
|
||||
console.log('Error case?');
|
||||
console.log(e);
|
||||
post.undoDeleteState();
|
||||
var response = $.parseJSON(e.responseText);
|
||||
if (response && response.errors) {
|
||||
bootbox.alert(response.errors[0]);
|
||||
} else {
|
||||
bootbox.alert(I18n.t('generic_error'));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -153,6 +153,7 @@ Discourse.Post = Discourse.Model.extend({
|
|||
// We're updating a post
|
||||
return Discourse.ajax("/posts/" + (this.get('id')), {
|
||||
type: 'PUT',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
post: { raw: this.get('raw'), edit_reason: this.get('editReason') },
|
||||
image_sizes: this.get('imageSizes')
|
||||
|
@ -236,6 +237,8 @@ Discourse.Post = Discourse.Model.extend({
|
|||
@param {Discourse.User} deletedBy The user deleting the post
|
||||
**/
|
||||
setDeletedState: function(deletedBy) {
|
||||
this.set('oldCooked', this.get('cooked'));
|
||||
|
||||
// Moderators can delete posts. Regular users can only trigger a deleted at message.
|
||||
if (deletedBy.get('staff')) {
|
||||
this.setProperties({
|
||||
|
@ -255,6 +258,26 @@ Discourse.Post = Discourse.Model.extend({
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
Changes the state of the post to NOT be deleted. Does not call the server.
|
||||
This can only be called after setDeletedState was called, but the delete
|
||||
failed on the server.
|
||||
|
||||
@method undoDeletedState
|
||||
**/
|
||||
undoDeleteState: function() {
|
||||
if (this.get('oldCooked')) {
|
||||
this.setProperties({
|
||||
deleted_at: null,
|
||||
deleted_by: null,
|
||||
cooked: this.get('oldCooked'),
|
||||
version: this.get('version') - 1,
|
||||
can_recover: false,
|
||||
user_deleted: false
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
Deletes a post
|
||||
|
||||
|
|
|
@ -80,15 +80,18 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
|
||||
rescue_from Discourse::NotFound do
|
||||
rescue_discourse_actions("[error: 'not found']", 404)
|
||||
rescue_discourse_actions("[error: 'not found']", 404) # TODO: this breaks json responses
|
||||
end
|
||||
|
||||
rescue_from Discourse::InvalidAccess do
|
||||
rescue_discourse_actions("[error: 'invalid access']", 403)
|
||||
rescue_discourse_actions("[error: 'invalid access']", 403) # TODO: this breaks json responses
|
||||
end
|
||||
|
||||
def rescue_discourse_actions(message, error)
|
||||
if request.format && request.format.json?
|
||||
# TODO: this doesn't make sense. Stuffing an html page into a json response will cause
|
||||
# $.parseJSON to fail in the browser. Also returning text like "[error: 'invalid access']"
|
||||
# from the above rescue_from blocks will fail because that isn't valid json.
|
||||
render status: error, layout: false, text: (error == 404) ? build_not_found_page(error) : message
|
||||
else
|
||||
render text: build_not_found_page(error, 'no_js')
|
||||
|
|
|
@ -63,6 +63,12 @@ class PostsController < ApplicationController
|
|||
post = post.with_deleted if guardian.is_staff?
|
||||
post = post.first
|
||||
post.image_sizes = params[:image_sizes] if params[:image_sizes].present?
|
||||
|
||||
if !guardian.can_edit?(post) && post.user_id == current_user.id && post.edit_time_limit_expired?
|
||||
render json: {errors: [I18n.t('too_late_to_edit')]}, status: 422
|
||||
return
|
||||
end
|
||||
|
||||
guardian.ensure_can_edit!(post)
|
||||
|
||||
# to stay consistent with the create api,
|
||||
|
@ -127,6 +133,12 @@ class PostsController < ApplicationController
|
|||
|
||||
def destroy
|
||||
post = find_post_from_params
|
||||
|
||||
if !guardian.can_delete_post?(post) && post.user_id == current_user.id && post.edit_time_limit_expired?
|
||||
render json: {errors: [I18n.t('too_late_to_edit')]}, status: 422
|
||||
return
|
||||
end
|
||||
|
||||
guardian.ensure_can_delete!(post)
|
||||
|
||||
destroyer = PostDestroyer.new(current_user, post)
|
||||
|
|
|
@ -413,6 +413,14 @@ class Post < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def edit_time_limit_expired?
|
||||
if created_at && SiteSetting.post_edit_time_limit.to_i > 0
|
||||
created_at < SiteSetting.post_edit_time_limit.to_i.minutes.ago
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_quote_into_arguments(quote)
|
||||
|
|
|
@ -85,6 +85,7 @@ en:
|
|||
rss_description:
|
||||
latest: "Latest topics"
|
||||
hot: "Hot topics"
|
||||
too_late_to_edit: "That post was created too long ago. It can no longer be edited or deleted."
|
||||
|
||||
groups:
|
||||
errors:
|
||||
|
@ -544,6 +545,7 @@ en:
|
|||
download_remote_images_to_local: "Download a copy of remote images hotlinked in posts"
|
||||
download_remote_images_threshold: "Amount of minimum available disk space required to download remote images locally (in percent)"
|
||||
ninja_edit_window: "Number of seconds after posting where edits do not create a new version"
|
||||
post_edit_time_limit: "Amount of time in minutes in which posts can be edited and deleted by the author. Set to 0 to allow editing and deleting posts at any time."
|
||||
edit_history_visible_to_public: "Allow everyone to see previous versions of an edited post. When disabled, only staff members can view edit history."
|
||||
delete_removed_posts_after: "Number of hours after which posts removed by the author will be deleted."
|
||||
max_image_width: "Maximum allowed width of images in a post"
|
||||
|
|
|
@ -164,6 +164,7 @@ posting:
|
|||
default: 15
|
||||
enable_private_messages: true
|
||||
ninja_edit_window: 300
|
||||
post_edit_time_limit: 525600
|
||||
edit_history_visible_to_public:
|
||||
client: true
|
||||
default: true
|
||||
|
|
|
@ -276,7 +276,7 @@ class Guardian
|
|||
end
|
||||
|
||||
def can_edit_post?(post)
|
||||
is_staff? || (!post.topic.archived? && is_my_own?(post) && !post.user_deleted &&!post.deleted_at)
|
||||
is_staff? || (!post.topic.archived? && is_my_own?(post) && !post.user_deleted && !post.deleted_at && !post.edit_time_limit_expired?)
|
||||
end
|
||||
|
||||
def can_edit_user?(user)
|
||||
|
@ -304,6 +304,9 @@ class Guardian
|
|||
# Can't delete the first post
|
||||
return false if post.post_number == 1
|
||||
|
||||
# Can't delete after post_edit_time_limit minutes have passed
|
||||
return false if !is_staff? && post.edit_time_limit_expired?
|
||||
|
||||
# You can delete your own posts
|
||||
return !post.user_deleted? if is_my_own?(post)
|
||||
|
||||
|
|
|
@ -544,6 +544,29 @@ describe Guardian do
|
|||
it 'returns true as an admin' do
|
||||
Guardian.new(admin).can_edit?(post).should be_true
|
||||
end
|
||||
|
||||
context 'post is older than post_edit_time_limit' do
|
||||
let(:old_post) { build(:post, topic: topic, user: topic.user, created_at: 6.minutes.ago) }
|
||||
before do
|
||||
SiteSetting.stubs(:post_edit_time_limit).returns(5)
|
||||
end
|
||||
|
||||
it 'returns false to the author of the post' do
|
||||
Guardian.new(old_post.user).can_edit?(old_post).should eq(false)
|
||||
end
|
||||
|
||||
it 'returns true as a moderator' do
|
||||
Guardian.new(moderator).can_edit?(old_post).should eq(true)
|
||||
end
|
||||
|
||||
it 'returns true as an admin' do
|
||||
Guardian.new(admin).can_edit?(old_post).should eq(true)
|
||||
end
|
||||
|
||||
it 'returns false for another regular user trying to edit your post' do
|
||||
Guardian.new(coding_horror).can_edit?(old_post).should eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'a Topic' do
|
||||
|
@ -773,6 +796,34 @@ describe Guardian do
|
|||
it 'returns true when an admin' do
|
||||
Guardian.new(admin).can_delete?(post).should be_true
|
||||
end
|
||||
|
||||
context 'post is older than post_edit_time_limit' do
|
||||
let(:old_post) { build(:post, topic: topic, user: topic.user, post_number: 2, created_at: 6.minutes.ago) }
|
||||
before do
|
||||
SiteSetting.stubs(:post_edit_time_limit).returns(5)
|
||||
end
|
||||
|
||||
it 'returns false to the author of the post' do
|
||||
Guardian.new(old_post.user).can_delete?(old_post).should eq(false)
|
||||
end
|
||||
|
||||
it 'returns true as a moderator' do
|
||||
Guardian.new(moderator).can_delete?(old_post).should eq(true)
|
||||
end
|
||||
|
||||
it 'returns true as an admin' do
|
||||
Guardian.new(admin).can_delete?(old_post).should eq(true)
|
||||
end
|
||||
|
||||
it "returns false when it's the OP, even as a moderator" do
|
||||
old_post.post_number = 1
|
||||
Guardian.new(moderator).can_delete?(old_post).should eq(false)
|
||||
end
|
||||
|
||||
it 'returns false for another regular user trying to delete your post' do
|
||||
Guardian.new(coding_horror).can_delete?(old_post).should eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'a Category' do
|
||||
|
|
|
@ -214,7 +214,7 @@ describe PostsController do
|
|||
end
|
||||
|
||||
it "raises an error when the user doesn't have permission to see the post" do
|
||||
Guardian.any_instance.expects(:can_edit?).with(post).returns(false)
|
||||
Guardian.any_instance.expects(:can_edit?).with(post).at_least_once.returns(false)
|
||||
xhr :put, :update, update_params
|
||||
response.should be_forbidden
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue