FEATURE: editing_grace_period_max_diff to force revisions in grace period
If a user performs a substantive edit of 20 chars or more during grace period we will store a revision to track the change This allows for better auditing of changes that happen during the grace period
This commit is contained in:
parent
c6cb7f6693
commit
e162cd16b6
|
@ -1023,6 +1023,7 @@ en:
|
||||||
download_remote_images_max_days_old: "Don't download remote images for posts that are more than n days old."
|
download_remote_images_max_days_old: "Don't download remote images for posts that are more than n days old."
|
||||||
disabled_image_download_domains: "Remote images will never be downloaded from these domains. Pipe-delimited list."
|
disabled_image_download_domains: "Remote images will never be downloaded from these domains. Pipe-delimited list."
|
||||||
editing_grace_period: "For (n) seconds after posting, editing will not create a new version in the post history."
|
editing_grace_period: "For (n) seconds after posting, editing will not create a new version in the post history."
|
||||||
|
editing_grace_period_max_diff: "Maximum number of character changes allowed in editing grace period, if more changed store another post revision"
|
||||||
staff_edit_locks_post: "Posts will be locked from editing if they are edited by staff members"
|
staff_edit_locks_post: "Posts will be locked from editing if they are edited by staff members"
|
||||||
post_edit_time_limit: "The author can edit or delete their post for (n) minutes after posting. Set to 0 for forever."
|
post_edit_time_limit: "The author can edit or delete their post for (n) minutes after posting. Set to 0 for forever."
|
||||||
edit_history_visible_to_public: "Allow everyone to see previous versions of an edited post. When disabled, only staff members can view."
|
edit_history_visible_to_public: "Allow everyone to see previous versions of an edited post. When disabled, only staff members can view."
|
||||||
|
|
|
@ -534,6 +534,7 @@ posting:
|
||||||
client: true
|
client: true
|
||||||
validator: "EnablePrivateEmailMessagesValidator"
|
validator: "EnablePrivateEmailMessagesValidator"
|
||||||
editing_grace_period: 300
|
editing_grace_period: 300
|
||||||
|
editing_grace_period_max_diff: 20
|
||||||
staff_edit_locks_post: false
|
staff_edit_locks_post: false
|
||||||
post_edit_time_limit:
|
post_edit_time_limit:
|
||||||
default: 86400
|
default: 86400
|
||||||
|
|
|
@ -214,7 +214,15 @@ class PostRevisor
|
||||||
end
|
end
|
||||||
|
|
||||||
def revise_post
|
def revise_post
|
||||||
should_create_new_version? ? revise_and_create_new_version : revise
|
if should_create_new_version?
|
||||||
|
revise_and_create_new_version
|
||||||
|
else
|
||||||
|
unless cached_original_raw
|
||||||
|
self.original_raw = @post.raw
|
||||||
|
self.original_cooked = @post.cooked
|
||||||
|
end
|
||||||
|
revise
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def should_create_new_version?
|
def should_create_new_version?
|
||||||
|
@ -226,9 +234,57 @@ class PostRevisor
|
||||||
@post.last_editor_id != @editor.id
|
@post.last_editor_id != @editor.id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def original_raw_key
|
||||||
|
"original_raw_#{(@last_version_at.to_f * 1000).to_i}#{@post.id}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def original_cooked_key
|
||||||
|
"original_cooked_#{(@last_version_at.to_f * 1000).to_i}#{@post.id}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def cached_original_raw
|
||||||
|
@cached_original_raw ||= $redis.get(original_raw_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cached_original_cooked
|
||||||
|
@cached_original_cooked ||= $redis.get(original_cooked_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
def original_raw
|
||||||
|
cached_original_raw || @post.raw
|
||||||
|
end
|
||||||
|
|
||||||
|
def original_raw=(val)
|
||||||
|
@cached_original_raw = val
|
||||||
|
$redis.setex(original_raw_key, SiteSetting.editing_grace_period + 1, val)
|
||||||
|
end
|
||||||
|
|
||||||
|
def original_cooked=(val)
|
||||||
|
@cached_original_cooked = val
|
||||||
|
$redis.setex(original_cooked_key, SiteSetting.editing_grace_period + 1, val)
|
||||||
|
end
|
||||||
|
|
||||||
|
def diff_size(before, after)
|
||||||
|
changes = 0
|
||||||
|
ONPDiff.new(before, after).short_diff.each do |str, type|
|
||||||
|
next if type == :common
|
||||||
|
changes += str.length
|
||||||
|
end
|
||||||
|
changes
|
||||||
|
end
|
||||||
|
|
||||||
def ninja_edit?
|
def ninja_edit?
|
||||||
return false if @post.has_active_flag?
|
return false if @post.has_active_flag?
|
||||||
@revised_at - @last_version_at <= SiteSetting.editing_grace_period.to_i
|
return false if (@revised_at - @last_version_at) > SiteSetting.editing_grace_period.to_i
|
||||||
|
|
||||||
|
if new_raw = @fields[:raw]
|
||||||
|
if (original_raw.length - new_raw.length).abs > SiteSetting.editing_grace_period_max_diff.to_i ||
|
||||||
|
diff_size(original_raw, new_raw) > SiteSetting.editing_grace_period_max_diff.to_i
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def owner_changed?
|
def owner_changed?
|
||||||
|
@ -368,6 +424,15 @@ class PostRevisor
|
||||||
|
|
||||||
def create_revision
|
def create_revision
|
||||||
modifications = post_changes.merge(@topic_changes.diff)
|
modifications = post_changes.merge(@topic_changes.diff)
|
||||||
|
|
||||||
|
if modifications["raw"]
|
||||||
|
modifications["raw"][0] = cached_original_raw || modifications["raw"][0]
|
||||||
|
end
|
||||||
|
|
||||||
|
if modifications["cooked"]
|
||||||
|
modifications["cooked"][0] = cached_original_cooked || modifications["cooked"][0]
|
||||||
|
end
|
||||||
|
|
||||||
PostRevision.create!(
|
PostRevision.create!(
|
||||||
user_id: @post.last_editor_id,
|
user_id: @post.last_editor_id,
|
||||||
post_id: @post.id,
|
post_id: @post.id,
|
||||||
|
@ -380,6 +445,7 @@ class PostRevisor
|
||||||
return unless revision = PostRevision.find_by(post_id: @post.id, number: @post.version)
|
return unless revision = PostRevision.find_by(post_id: @post.id, number: @post.version)
|
||||||
revision.user_id = @post.last_editor_id
|
revision.user_id = @post.last_editor_id
|
||||||
modifications = post_changes.merge(@topic_changes.diff)
|
modifications = post_changes.merge(@topic_changes.diff)
|
||||||
|
|
||||||
modifications.each_key do |field|
|
modifications.each_key do |field|
|
||||||
if revision.modifications.has_key?(field)
|
if revision.modifications.has_key?(field)
|
||||||
old_value = revision.modifications[field][0].to_s
|
old_value = revision.modifications[field][0].to_s
|
||||||
|
|
|
@ -111,8 +111,30 @@ describe PostRevisor do
|
||||||
expect(subject.category_changed).to be_blank
|
expect(subject.category_changed).to be_blank
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "does create a new version if a large diff happens" do
|
||||||
|
SiteSetting.editing_grace_period_max_diff = 10
|
||||||
|
|
||||||
|
post = Fabricate(:post, raw: 'hello world')
|
||||||
|
revisor = PostRevisor.new(post)
|
||||||
|
revisor.revise!(post.user, { raw: 'hello world123456789' }, revised_at: post.updated_at + 1.second)
|
||||||
|
|
||||||
|
post.reload
|
||||||
|
|
||||||
|
expect(post.version).to eq(1)
|
||||||
|
|
||||||
|
revisor = PostRevisor.new(post)
|
||||||
|
revisor.revise!(post.user, { raw: 'hello world12345678901' }, revised_at: post.updated_at + 1.second)
|
||||||
|
|
||||||
|
post.reload
|
||||||
|
expect(post.version).to eq(2)
|
||||||
|
|
||||||
|
expect(post.revisions.first.modifications["raw"][0]).to eq("hello world")
|
||||||
|
expect(post.revisions.first.modifications["cooked"][0]).to eq("<p>hello world</p>")
|
||||||
|
end
|
||||||
|
|
||||||
it "doesn't create a new version" do
|
it "doesn't create a new version" do
|
||||||
SiteSetting.editing_grace_period = 1.minute
|
SiteSetting.editing_grace_period = 1.minute
|
||||||
|
SiteSetting.editing_grace_period_max_diff = 100
|
||||||
|
|
||||||
# making a revision
|
# making a revision
|
||||||
subject.revise!(post.user, { raw: 'updated body' }, revised_at: post.updated_at + SiteSetting.editing_grace_period + 1.seconds)
|
subject.revise!(post.user, { raw: 'updated body' }, revised_at: post.updated_at + SiteSetting.editing_grace_period + 1.seconds)
|
||||||
|
@ -317,48 +339,34 @@ describe PostRevisor do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'with a new body' do
|
describe 'with a new body' do
|
||||||
|
before do
|
||||||
|
SiteSetting.editing_grace_period_max_diff = 1000
|
||||||
|
end
|
||||||
|
|
||||||
let(:changed_by) { Fabricate(:coding_horror) }
|
let(:changed_by) { Fabricate(:coding_horror) }
|
||||||
let!(:result) { subject.revise!(changed_by, raw: "lets update the body. Здравствуйте") }
|
let!(:result) { subject.revise!(changed_by, raw: "lets update the body. Здравствуйте") }
|
||||||
|
|
||||||
it 'returns true' do
|
it 'correctly updates raw' do
|
||||||
expect(result).to eq(true)
|
expect(result).to eq(true)
|
||||||
end
|
|
||||||
|
|
||||||
it 'updates the body' do
|
|
||||||
expect(post.raw).to eq("lets update the body. Здравствуйте")
|
expect(post.raw).to eq("lets update the body. Здравствуйте")
|
||||||
end
|
|
||||||
|
|
||||||
it 'sets the invalidate oneboxes attribute' do
|
|
||||||
expect(post.invalidate_oneboxes).to eq(true)
|
expect(post.invalidate_oneboxes).to eq(true)
|
||||||
end
|
|
||||||
|
|
||||||
it 'increased the versions' do
|
|
||||||
expect(post.version).to eq(2)
|
expect(post.version).to eq(2)
|
||||||
expect(post.public_version).to eq(2)
|
expect(post.public_version).to eq(2)
|
||||||
end
|
|
||||||
|
|
||||||
it 'has the new revision' do
|
|
||||||
expect(post.revisions.size).to eq(1)
|
expect(post.revisions.size).to eq(1)
|
||||||
end
|
|
||||||
|
|
||||||
it "saved the user who made the change in the revisions" do
|
|
||||||
expect(post.revisions.first.user_id).to eq(changed_by.id)
|
expect(post.revisions.first.user_id).to eq(changed_by.id)
|
||||||
end
|
|
||||||
|
|
||||||
it "updates the word count" do
|
# updates word count
|
||||||
expect(post.word_count).to eq(5)
|
expect(post.word_count).to eq(5)
|
||||||
post.topic.reload
|
post.topic.reload
|
||||||
expect(post.topic.word_count).to eq(5)
|
expect(post.topic.word_count).to eq(5)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'second poster posts again quickly' do
|
context 'second poster posts again quickly' do
|
||||||
before do
|
|
||||||
|
it 'is a ninja edit, because the second poster posted again quickly' do
|
||||||
SiteSetting.editing_grace_period = 1.minute
|
SiteSetting.editing_grace_period = 1.minute
|
||||||
subject.revise!(changed_by, { raw: 'yet another updated body' }, revised_at: post.updated_at + 10.seconds)
|
subject.revise!(changed_by, { raw: 'yet another updated body' }, revised_at: post.updated_at + 10.seconds)
|
||||||
post.reload
|
post.reload
|
||||||
end
|
|
||||||
|
|
||||||
it 'is a ninja edit, because the second poster posted again quickly' do
|
|
||||||
expect(post.version).to eq(2)
|
expect(post.version).to eq(2)
|
||||||
expect(post.public_version).to eq(2)
|
expect(post.public_version).to eq(2)
|
||||||
expect(post.revisions.size).to eq(1)
|
expect(post.revisions.size).to eq(1)
|
||||||
|
|
|
@ -674,7 +674,7 @@ describe Post do
|
||||||
context 'second poster posts again quickly' do
|
context 'second poster posts again quickly' do
|
||||||
|
|
||||||
it 'is a ninja edit, because the second poster posted again quickly' do
|
it 'is a ninja edit, because the second poster posted again quickly' do
|
||||||
SiteSetting.expects(:editing_grace_period).returns(1.minute.to_i)
|
SiteSetting.editing_grace_period = 1.minute.to_i
|
||||||
post.revise(changed_by, { raw: 'yet another updated body' }, revised_at: post.updated_at + 10.seconds)
|
post.revise(changed_by, { raw: 'yet another updated body' }, revised_at: post.updated_at + 10.seconds)
|
||||||
post.reload
|
post.reload
|
||||||
|
|
||||||
|
|
|
@ -734,8 +734,8 @@ describe Topic do
|
||||||
|
|
||||||
it "doesn't bump the topic on an edit to the last post that doesn't result in a new version" do
|
it "doesn't bump the topic on an edit to the last post that doesn't result in a new version" do
|
||||||
expect {
|
expect {
|
||||||
SiteSetting.expects(:editing_grace_period).returns(5.minutes)
|
SiteSetting.editing_grace_period = 5.minutes
|
||||||
@last_post.revise(@last_post.user, { raw: 'updated contents' }, revised_at: @last_post.created_at + 10.seconds)
|
@last_post.revise(@last_post.user, { raw: @last_post.raw + "a" }, revised_at: @last_post.created_at + 10.seconds)
|
||||||
@topic.reload
|
@topic.reload
|
||||||
}.not_to change(@topic, :bumped_at)
|
}.not_to change(@topic, :bumped_at)
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue