2019-05-02 18:17:27 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2014-10-27 17:06:43 -04:00
|
|
|
require "edit_rate_limiter"
|
2018-01-26 13:45:52 -05:00
|
|
|
require 'post_locker'
|
2013-12-11 21:41:34 -05:00
|
|
|
|
2013-02-09 10:33:07 -05:00
|
|
|
class PostRevisor
|
2013-02-21 18:09:56 -05:00
|
|
|
|
2015-01-27 12:13:45 -05:00
|
|
|
# Helps us track changes to a topic.
|
|
|
|
#
|
2015-01-28 11:01:01 -05:00
|
|
|
# It's passed to `track_topic_fields` callbacks so they can record if they
|
2015-01-27 12:13:45 -05:00
|
|
|
# changed a value or not. This is needed for things like custom fields.
|
|
|
|
class TopicChanges
|
|
|
|
attr_reader :topic, :user
|
|
|
|
|
|
|
|
def initialize(topic, user)
|
|
|
|
@topic = topic
|
|
|
|
@user = user
|
|
|
|
@changed = {}
|
|
|
|
@errored = false
|
|
|
|
end
|
|
|
|
|
|
|
|
def errored?
|
|
|
|
@errored
|
|
|
|
end
|
|
|
|
|
|
|
|
def guardian
|
|
|
|
@guardian ||= Guardian.new(@user)
|
|
|
|
end
|
|
|
|
|
|
|
|
def record_change(field_name, previous_val, new_val)
|
|
|
|
return if previous_val == new_val
|
|
|
|
diff[field_name] = [previous_val, new_val]
|
|
|
|
end
|
|
|
|
|
|
|
|
def check_result(res)
|
|
|
|
@errored = true if !res
|
|
|
|
end
|
|
|
|
|
|
|
|
def diff
|
|
|
|
@diff ||= {}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-10-27 17:06:43 -04:00
|
|
|
POST_TRACKED_FIELDS = %w{raw cooked edit_reason user_id wiki post_type}
|
|
|
|
|
2020-05-11 04:32:41 -04:00
|
|
|
attr_reader :category_changed, :post_revision
|
2013-02-21 18:09:56 -05:00
|
|
|
|
2020-04-20 21:50:20 -04:00
|
|
|
def initialize(post, topic = post.topic)
|
2013-02-09 10:33:07 -05:00
|
|
|
@post = post
|
2020-04-20 21:50:20 -04:00
|
|
|
@topic = topic
|
|
|
|
|
|
|
|
# Make sure we have only one Topic instance
|
|
|
|
post.topic = topic
|
2013-02-09 10:33:07 -05:00
|
|
|
end
|
|
|
|
|
2015-01-28 11:01:01 -05:00
|
|
|
def self.tracked_topic_fields
|
|
|
|
@@tracked_topic_fields ||= {}
|
|
|
|
@@tracked_topic_fields
|
2015-01-27 12:13:45 -05:00
|
|
|
end
|
|
|
|
|
2015-01-28 11:01:01 -05:00
|
|
|
def self.track_topic_field(field, &block)
|
|
|
|
tracked_topic_fields[field] = block
|
|
|
|
|
|
|
|
# Define it in the serializer unless it already has been defined
|
|
|
|
unless PostRevisionSerializer.instance_methods(false).include?("#{field}_changes".to_sym)
|
|
|
|
PostRevisionSerializer.add_compared_field(field)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-08-05 05:38:39 -04:00
|
|
|
def self.track_and_revise(topic_changes, field, attribute)
|
|
|
|
topic_changes.record_change(
|
|
|
|
field,
|
|
|
|
topic_changes.topic.public_send(field),
|
|
|
|
attribute
|
|
|
|
)
|
|
|
|
topic_changes.topic.public_send("#{field}=", attribute)
|
|
|
|
end
|
|
|
|
|
|
|
|
track_topic_field(:title) do |topic_changes, attribute|
|
2021-08-06 12:07:42 -04:00
|
|
|
track_and_revise topic_changes, :title, attribute
|
2015-01-28 11:01:01 -05:00
|
|
|
end
|
|
|
|
|
2021-08-05 05:38:39 -04:00
|
|
|
track_topic_field(:archetype) do |topic_changes, attribute|
|
|
|
|
track_and_revise topic_changes, :archetype, attribute
|
|
|
|
end
|
|
|
|
|
2021-03-02 18:59:23 -05:00
|
|
|
track_topic_field(:category_id) do |tc, category_id, fields|
|
2019-04-16 03:16:23 -04:00
|
|
|
if category_id == 0 && tc.topic.private_message?
|
|
|
|
tc.record_change('category_id', tc.topic.category_id, nil)
|
|
|
|
tc.topic.category_id = nil
|
|
|
|
elsif category_id == 0 || tc.guardian.can_move_topic_to_category?(category_id)
|
2021-03-02 18:59:23 -05:00
|
|
|
tags = fields[:tags] || tc.topic.tags.map(&:name)
|
|
|
|
if category_id != 0 && !DiscourseTagging.validate_min_required_tags_for_category(tc.guardian, tc.topic, Category.find(category_id), tags)
|
|
|
|
tc.check_result(false)
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
2018-03-01 20:13:04 -05:00
|
|
|
tc.record_change('category_id', tc.topic.category_id, category_id)
|
|
|
|
tc.check_result(tc.topic.change_category_to_id(category_id))
|
|
|
|
end
|
2015-01-27 12:13:45 -05:00
|
|
|
end
|
|
|
|
|
2016-04-25 15:55:15 -04:00
|
|
|
track_topic_field(:tags) do |tc, tags|
|
2016-05-04 14:02:47 -04:00
|
|
|
if tc.guardian.can_tag_topics?
|
|
|
|
prev_tags = tc.topic.tags.map(&:name)
|
|
|
|
next if tags.blank? && prev_tags.blank?
|
|
|
|
if !DiscourseTagging.tag_topic_by_names(tc.topic, tc.guardian, tags)
|
|
|
|
tc.check_result(false)
|
|
|
|
next
|
2016-04-25 15:55:15 -04:00
|
|
|
end
|
2019-03-12 13:09:34 -04:00
|
|
|
if prev_tags.sort != tags.sort
|
|
|
|
tc.record_change('tags', prev_tags, tags)
|
|
|
|
DB.after_commit do
|
|
|
|
post = tc.topic.ordered_posts.first
|
2019-11-06 16:20:15 -05:00
|
|
|
notified_user_ids = [post.user_id, post.last_editor_id].uniq
|
2021-02-10 18:03:45 -05:00
|
|
|
Jobs.enqueue(:notify_tag_change, post_id: post.id, notified_user_ids: notified_user_ids, diff_tags: ((tags - prev_tags) | (prev_tags - tags)))
|
2019-03-12 13:09:34 -04:00
|
|
|
end
|
|
|
|
end
|
2016-04-25 15:55:15 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-12-05 07:31:43 -05:00
|
|
|
track_topic_field(:featured_link) do |topic_changes, featured_link|
|
2021-08-05 05:38:39 -04:00
|
|
|
if !SiteSetting.topic_featured_link_enabled ||
|
|
|
|
!topic_changes.guardian.can_edit_featured_link?(topic_changes.topic.category_id)
|
|
|
|
topic_changes.check_result(false)
|
|
|
|
else
|
2016-12-05 07:31:43 -05:00
|
|
|
topic_changes.record_change('featured_link', topic_changes.topic.featured_link, featured_link)
|
|
|
|
topic_changes.topic.featured_link = featured_link
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-10-27 17:06:43 -04:00
|
|
|
# AVAILABLE OPTIONS:
|
|
|
|
# - revised_at: changes the date of the revision
|
2021-06-30 21:27:11 -04:00
|
|
|
# - force_new_version: bypass grace period edit window
|
2014-10-27 17:06:43 -04:00
|
|
|
# - bypass_rate_limiter:
|
|
|
|
# - bypass_bump: do not bump the topic, even if last post
|
|
|
|
# - skip_validations: ask ActiveRecord to skip validations
|
2016-08-19 13:13:22 -04:00
|
|
|
# - skip_revision: do not create a new PostRevision record
|
2019-09-12 10:55:45 -04:00
|
|
|
# - skip_staff_log: skip creating an entry in the staff action log
|
2014-10-27 17:06:43 -04:00
|
|
|
def revise!(editor, fields, opts = {})
|
2014-08-11 18:01:58 -04:00
|
|
|
@editor = editor
|
2014-10-27 17:06:43 -04:00
|
|
|
@fields = fields.with_indifferent_access
|
2014-08-11 18:01:58 -04:00
|
|
|
@opts = opts
|
2014-10-27 17:06:43 -04:00
|
|
|
|
2015-01-27 12:13:45 -05:00
|
|
|
@topic_changes = TopicChanges.new(@topic, editor)
|
|
|
|
|
2014-10-27 17:06:43 -04:00
|
|
|
# some normalization
|
|
|
|
@fields[:raw] = cleanup_whitespaces(@fields[:raw]) if @fields.has_key?(:raw)
|
|
|
|
@fields[:user_id] = @fields[:user_id].to_i if @fields.has_key?(:user_id)
|
|
|
|
@fields[:category_id] = @fields[:category_id].to_i if @fields.has_key?(:category_id)
|
|
|
|
|
2019-11-17 22:08:54 -05:00
|
|
|
# always reset edit_reason unless provided, do not set to nil else
|
|
|
|
# previous reasons are lost
|
|
|
|
@fields.delete(:edit_reason) if @fields[:edit_reason].blank?
|
2014-08-11 18:01:58 -04:00
|
|
|
|
2021-03-24 11:22:16 -04:00
|
|
|
Post.plugin_permitted_update_params.each do |field, val|
|
|
|
|
if @fields.key?(field) && val[:plugin].enabled?
|
|
|
|
val[:handler].call(@post, @fields[field])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-03-28 01:45:51 -04:00
|
|
|
return false unless should_revise?
|
2014-08-29 00:27:40 -04:00
|
|
|
|
2014-09-03 20:43:57 -04:00
|
|
|
@post.acting_user = @editor
|
2014-10-27 17:06:43 -04:00
|
|
|
@topic.acting_user = @editor
|
|
|
|
@revised_at = @opts[:revised_at] || Time.now
|
|
|
|
@last_version_at = @post.last_version_at || Time.now
|
|
|
|
|
2021-06-30 21:27:11 -04:00
|
|
|
if guardian.affected_by_slow_mode?(@topic) && !grace_period_edit? && SiteSetting.slow_mode_prevents_editing
|
2020-10-28 15:47:50 -04:00
|
|
|
@post.errors.add(:base, I18n.t("cannot_edit_on_slow_mode"))
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2014-10-27 17:06:43 -04:00
|
|
|
@version_changed = false
|
|
|
|
@post_successfully_saved = true
|
2014-09-03 20:43:57 -04:00
|
|
|
|
2014-11-25 10:53:26 -05:00
|
|
|
@validate_post = true
|
|
|
|
@validate_post = @opts[:validate_post] if @opts.has_key?(:validate_post)
|
|
|
|
@validate_post = !@opts[:skip_validations] if @opts.has_key?(:skip_validations)
|
|
|
|
|
|
|
|
@validate_topic = true
|
|
|
|
@validate_topic = @opts[:validate_topic] if @opts.has_key?(:validate_topic)
|
2021-05-23 19:10:22 -04:00
|
|
|
@validate_topic = !@opts[:skip_validations] if @opts.has_key?(:skip_validations)
|
2014-11-17 10:06:43 -05:00
|
|
|
|
2016-08-19 13:13:22 -04:00
|
|
|
@skip_revision = false
|
|
|
|
@skip_revision = @opts[:skip_revision] if @opts.has_key?(:skip_revision)
|
|
|
|
|
2020-07-10 05:05:55 -04:00
|
|
|
if @post.incoming_email&.imap_uid
|
|
|
|
@post.incoming_email&.update(imap_sync: true)
|
|
|
|
end
|
|
|
|
|
2018-03-14 15:01:36 -04:00
|
|
|
old_raw = @post.raw
|
|
|
|
|
2014-08-29 00:27:40 -04:00
|
|
|
Post.transaction do
|
|
|
|
revise_post
|
2014-09-03 20:43:57 -04:00
|
|
|
|
2016-08-10 01:55:51 -04:00
|
|
|
yield if block_given?
|
2014-10-27 17:06:43 -04:00
|
|
|
# TODO: these callbacks are being called in a transaction
|
|
|
|
# it is kind of odd, because the callback is called "before_edit"
|
|
|
|
# but the post is already edited at this point
|
|
|
|
# Trouble is that much of the logic of should I edit? is deeper
|
|
|
|
# down so yanking this in front of the transaction will lead to
|
|
|
|
# false positive.
|
2014-08-29 00:27:40 -04:00
|
|
|
plugin_callbacks
|
2014-09-03 20:43:57 -04:00
|
|
|
|
2014-10-27 17:06:43 -04:00
|
|
|
revise_topic
|
|
|
|
advance_draft_sequence
|
2014-08-29 00:27:40 -04:00
|
|
|
end
|
|
|
|
|
2018-01-26 13:45:52 -05:00
|
|
|
# Lock the post by default if the appropriate setting is true
|
2018-03-01 20:33:04 -05:00
|
|
|
if (
|
|
|
|
SiteSetting.staff_edit_locks_post? &&
|
2018-03-05 14:50:06 -05:00
|
|
|
!@post.wiki? &&
|
2018-03-01 20:33:04 -05:00
|
|
|
@fields.has_key?('raw') &&
|
|
|
|
@editor.staff? &&
|
2018-12-02 14:59:00 -05:00
|
|
|
@editor != Discourse.system_user &&
|
2020-03-11 08:03:20 -04:00
|
|
|
!@post.user&.staff?
|
2018-03-01 20:33:04 -05:00
|
|
|
)
|
2018-01-26 13:45:52 -05:00
|
|
|
PostLocker.new(@post, @editor).lock
|
|
|
|
end
|
|
|
|
|
2020-07-23 09:50:00 -04:00
|
|
|
# We log staff/group moderator edits to posts
|
|
|
|
if (
|
2020-10-28 15:47:50 -04:00
|
|
|
(@editor.staff? || (@post.is_category_description? && guardian.can_edit_category_description?(@post.topic.category))) &&
|
2020-07-23 09:50:00 -04:00
|
|
|
@editor.id != @post.user_id &&
|
|
|
|
@fields.has_key?('raw') &&
|
|
|
|
!@opts[:skip_staff_log]
|
|
|
|
)
|
2018-03-12 13:49:52 -04:00
|
|
|
StaffActionLogger.new(@editor).log_post_edit(
|
|
|
|
@post,
|
2018-03-14 15:01:36 -04:00
|
|
|
old_raw: old_raw
|
2018-03-12 13:49:52 -04:00
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2014-10-27 17:06:43 -04:00
|
|
|
# WARNING: do not pull this into the transaction
|
|
|
|
# it can fire events in sidekiq before the post is done saving
|
|
|
|
# leading to corrupt state
|
2018-05-25 05:31:45 -04:00
|
|
|
QuotedPost.extract_from(@post)
|
2021-05-20 23:32:32 -04:00
|
|
|
|
|
|
|
# This must be done before post_process_post, because that uses
|
|
|
|
# post upload security status to cook URLs.
|
|
|
|
@post.update_uploads_secure_status(source: "post revisor")
|
|
|
|
|
2014-09-03 20:43:57 -04:00
|
|
|
post_process_post
|
2014-10-27 17:06:43 -04:00
|
|
|
|
2014-09-03 20:43:57 -04:00
|
|
|
update_topic_word_counts
|
2014-10-27 17:06:43 -04:00
|
|
|
alert_users
|
|
|
|
publish_changes
|
|
|
|
grant_badge
|
2014-09-03 20:43:57 -04:00
|
|
|
|
2015-08-14 13:33:32 -04:00
|
|
|
TopicLink.extract_from(@post)
|
|
|
|
|
2021-04-21 07:41:36 -04:00
|
|
|
if should_create_new_version?
|
|
|
|
ReviewablePost.queue_for_review_if_possible(@post, @editor)
|
|
|
|
end
|
|
|
|
|
2014-11-03 09:31:11 -05:00
|
|
|
successfully_saved_post_and_topic
|
2014-10-27 17:06:43 -04:00
|
|
|
end
|
2014-09-03 20:43:57 -04:00
|
|
|
|
2014-10-27 17:06:43 -04:00
|
|
|
def cleanup_whitespaces(raw)
|
2019-01-19 07:38:25 -05:00
|
|
|
raw.present? ? TextCleaner.normalize_whitespaces(raw).gsub(/\s+\z/, "") : ""
|
2014-10-27 17:06:43 -04:00
|
|
|
end
|
2013-12-11 21:41:34 -05:00
|
|
|
|
2014-10-27 17:06:43 -04:00
|
|
|
def should_revise?
|
|
|
|
post_changed? || topic_changed?
|
2013-02-09 10:33:07 -05:00
|
|
|
end
|
|
|
|
|
2014-10-27 17:06:43 -04:00
|
|
|
def post_changed?
|
|
|
|
POST_TRACKED_FIELDS.each do |field|
|
2019-05-06 21:27:05 -04:00
|
|
|
if @fields.has_key?(field) && @fields[field] != @post.public_send(field)
|
|
|
|
return true
|
|
|
|
end
|
2014-10-27 17:06:43 -04:00
|
|
|
end
|
2016-03-14 20:31:50 -04:00
|
|
|
advance_draft_sequence
|
2014-10-27 17:06:43 -04:00
|
|
|
false
|
|
|
|
end
|
2013-02-09 10:33:07 -05:00
|
|
|
|
2014-10-27 17:06:43 -04:00
|
|
|
def topic_changed?
|
2015-01-28 11:01:01 -05:00
|
|
|
PostRevisor.tracked_topic_fields.keys.any? { |f| @fields.has_key?(f) }
|
2013-02-09 10:33:07 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def revise_post
|
2018-03-07 00:44:21 -05:00
|
|
|
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
|
2013-02-09 10:33:07 -05:00
|
|
|
end
|
|
|
|
|
2014-10-27 17:06:43 -04:00
|
|
|
def should_create_new_version?
|
2016-08-19 13:13:22 -04:00
|
|
|
return false if @skip_revision
|
2021-06-30 21:27:11 -04:00
|
|
|
edited_by_another_user? || !grace_period_edit? || owner_changed? || force_new_version? || edit_reason_specified?
|
2019-11-17 22:08:54 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def edit_reason_specified?
|
|
|
|
@fields[:edit_reason].present? && @fields[:edit_reason] != @post.edit_reason
|
2014-08-29 00:27:40 -04:00
|
|
|
end
|
|
|
|
|
2014-10-27 17:06:43 -04:00
|
|
|
def edited_by_another_user?
|
|
|
|
@post.last_editor_id != @editor.id
|
2013-02-09 10:33:07 -05:00
|
|
|
end
|
|
|
|
|
2018-03-07 00:44:21 -05:00
|
|
|
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
|
2019-12-03 04:05:53 -05:00
|
|
|
@cached_original_raw ||= Discourse.redis.get(original_raw_key)
|
2018-03-07 00:44:21 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def cached_original_cooked
|
2019-12-03 04:05:53 -05:00
|
|
|
@cached_original_cooked ||= Discourse.redis.get(original_cooked_key)
|
2018-03-07 00:44:21 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def original_raw
|
|
|
|
cached_original_raw || @post.raw
|
|
|
|
end
|
|
|
|
|
|
|
|
def original_raw=(val)
|
|
|
|
@cached_original_raw = val
|
2019-12-03 04:05:53 -05:00
|
|
|
Discourse.redis.setex(original_raw_key, SiteSetting.editing_grace_period + 1, val)
|
2018-03-07 00:44:21 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def original_cooked=(val)
|
|
|
|
@cached_original_cooked = val
|
2019-12-03 04:05:53 -05:00
|
|
|
Discourse.redis.setex(original_cooked_key, SiteSetting.editing_grace_period + 1, val)
|
2018-03-07 00:44:21 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def diff_size(before, after)
|
2020-02-12 12:47:48 -05:00
|
|
|
@diff_size ||= begin
|
|
|
|
ONPDiff.new(before, after).short_diff.sum do |str, type|
|
|
|
|
type == :common ? 0 : str.size
|
|
|
|
end
|
2018-03-07 00:44:21 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-06-30 21:27:11 -04:00
|
|
|
def grace_period_edit?
|
2018-03-07 00:44:21 -05:00
|
|
|
return false if (@revised_at - @last_version_at) > SiteSetting.editing_grace_period.to_i
|
2019-01-03 12:03:01 -05:00
|
|
|
return false if @post.reviewable_flag.present?
|
2018-03-07 00:44:21 -05:00
|
|
|
|
|
|
|
if new_raw = @fields[:raw]
|
2018-03-08 19:58:50 -05:00
|
|
|
|
|
|
|
max_diff = SiteSetting.editing_grace_period_max_diff.to_i
|
|
|
|
if @editor.staff? || (@editor.trust_level > 1)
|
|
|
|
max_diff = SiteSetting.editing_grace_period_max_diff_high_trust.to_i
|
|
|
|
end
|
|
|
|
|
2019-06-26 19:45:52 -04:00
|
|
|
if (original_raw.size - new_raw.size).abs > max_diff ||
|
2018-03-08 19:58:50 -05:00
|
|
|
diff_size(original_raw, new_raw) > max_diff
|
2018-03-07 00:44:21 -05:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
true
|
2014-10-27 17:06:43 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def owner_changed?
|
|
|
|
@fields.has_key?(:user_id) && @fields[:user_id] != @post.user_id
|
|
|
|
end
|
|
|
|
|
|
|
|
def force_new_version?
|
2013-11-21 19:52:26 -05:00
|
|
|
@opts[:force_new_version] == true
|
2013-02-09 10:33:07 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def revise_and_create_new_version
|
2014-10-27 17:06:43 -04:00
|
|
|
@version_changed = true
|
2014-09-03 20:43:57 -04:00
|
|
|
@post.version += 1
|
2014-10-27 17:06:43 -04:00
|
|
|
@post.public_version += 1
|
|
|
|
@post.last_version_at = @revised_at
|
|
|
|
|
|
|
|
revise
|
|
|
|
perform_edit
|
|
|
|
bump_topic
|
|
|
|
end
|
|
|
|
|
|
|
|
def revise
|
2014-09-03 20:43:57 -04:00
|
|
|
update_post
|
2014-10-27 17:06:43 -04:00
|
|
|
update_topic if topic_changed?
|
|
|
|
create_or_update_revision
|
2020-01-27 11:05:48 -05:00
|
|
|
remove_flags_and_unhide_post
|
2013-02-09 10:33:07 -05:00
|
|
|
end
|
|
|
|
|
2015-05-29 17:39:24 -04:00
|
|
|
USER_ACTIONS_TO_REMOVE ||= [UserAction::REPLY, UserAction::RESPONSE]
|
2015-05-29 14:08:39 -04:00
|
|
|
|
2014-10-27 17:06:43 -04:00
|
|
|
def update_post
|
2015-07-15 18:22:01 -04:00
|
|
|
if @fields.has_key?("user_id") && @fields["user_id"] != @post.user_id && @post.user_id != nil
|
2015-05-29 14:08:39 -04:00
|
|
|
prev_owner = User.find(@post.user_id)
|
|
|
|
new_owner = User.find(@fields["user_id"])
|
2015-03-11 15:54:11 -04:00
|
|
|
|
2015-05-29 14:08:39 -04:00
|
|
|
UserAction.where(target_post_id: @post.id)
|
|
|
|
.where(user_id: prev_owner.id)
|
|
|
|
.where(action_type: USER_ACTIONS_TO_REMOVE)
|
2019-01-03 12:03:01 -05:00
|
|
|
.update_all(user_id: new_owner.id)
|
2015-05-29 17:39:24 -04:00
|
|
|
|
|
|
|
if @post.post_number == 1
|
|
|
|
UserAction.where(target_topic_id: @post.topic_id)
|
|
|
|
.where(user_id: prev_owner.id)
|
|
|
|
.where(action_type: UserAction::NEW_TOPIC)
|
2019-01-03 12:03:01 -05:00
|
|
|
.update_all(user_id: new_owner.id)
|
2015-05-29 17:39:24 -04:00
|
|
|
end
|
2015-03-02 11:17:11 -05:00
|
|
|
end
|
|
|
|
|
2014-10-27 17:06:43 -04:00
|
|
|
POST_TRACKED_FIELDS.each do |field|
|
2020-02-14 06:05:52 -05:00
|
|
|
if @fields.has_key?(field)
|
2020-02-11 12:41:59 -05:00
|
|
|
@post.public_send("#{field}=", @fields[field])
|
|
|
|
end
|
2013-02-09 10:33:07 -05:00
|
|
|
end
|
2014-10-27 17:06:43 -04:00
|
|
|
|
2020-02-14 06:05:52 -05:00
|
|
|
@post.edit_reason = @fields[:edit_reason] if should_create_new_version?
|
2014-10-27 17:06:43 -04:00
|
|
|
@post.last_editor_id = @editor.id
|
2016-01-11 05:16:23 -05:00
|
|
|
@post.word_count = @fields[:raw].scan(/[[:word:]]+/).size if @fields.has_key?(:raw)
|
2014-10-27 17:06:43 -04:00
|
|
|
@post.self_edits += 1 if self_edit?
|
|
|
|
|
|
|
|
@post.extract_quoted_post_numbers
|
2015-02-02 12:44:21 -05:00
|
|
|
|
2014-11-17 10:06:43 -05:00
|
|
|
@post_successfully_saved = @post.save(validate: @validate_post)
|
2020-09-11 05:48:25 -04:00
|
|
|
@post.link_post_uploads
|
2014-10-27 17:06:43 -04:00
|
|
|
@post.save_reply_relationships
|
2015-03-11 15:54:11 -04:00
|
|
|
|
2021-08-02 10:15:53 -04:00
|
|
|
@editor.increment_post_edits_count if @post_successfully_saved
|
|
|
|
|
2015-03-11 15:54:11 -04:00
|
|
|
# post owner changed
|
|
|
|
if prev_owner && new_owner && prev_owner != new_owner
|
2015-05-29 14:08:39 -04:00
|
|
|
likes = UserAction.where(target_post_id: @post.id)
|
|
|
|
.where(user_id: prev_owner.id)
|
|
|
|
.where(action_type: UserAction::WAS_LIKED)
|
|
|
|
.update_all(user_id: new_owner.id)
|
2015-03-11 15:54:11 -04:00
|
|
|
|
2020-04-20 21:50:20 -04:00
|
|
|
private_message = @topic.private_message?
|
2017-01-15 21:18:10 -05:00
|
|
|
|
|
|
|
prev_owner_user_stat = prev_owner.user_stat
|
2017-11-02 17:48:48 -04:00
|
|
|
unless private_message
|
|
|
|
prev_owner_user_stat.post_count -= 1 if @post.post_type == Post.types[:regular]
|
|
|
|
prev_owner_user_stat.topic_count -= 1 if @post.is_first_post?
|
|
|
|
prev_owner_user_stat.likes_received -= likes
|
|
|
|
end
|
2015-05-29 14:08:39 -04:00
|
|
|
|
2015-03-11 15:54:11 -04:00
|
|
|
if @post.created_at == prev_owner.user_stat.first_post_created_at
|
2017-01-15 21:18:10 -05:00
|
|
|
prev_owner_user_stat.first_post_created_at = prev_owner.posts.order('created_at ASC').first.try(:created_at)
|
2015-03-11 15:54:11 -04:00
|
|
|
end
|
2015-05-29 14:08:39 -04:00
|
|
|
|
2017-01-15 21:18:10 -05:00
|
|
|
prev_owner_user_stat.save!
|
2015-03-11 15:54:11 -04:00
|
|
|
|
2017-01-15 21:18:10 -05:00
|
|
|
new_owner_user_stat = new_owner.user_stat
|
2017-11-02 17:48:48 -04:00
|
|
|
unless private_message
|
|
|
|
new_owner_user_stat.post_count += 1 if @post.post_type == Post.types[:regular]
|
|
|
|
new_owner_user_stat.topic_count += 1 if @post.is_first_post?
|
|
|
|
new_owner_user_stat.likes_received += likes
|
|
|
|
end
|
2017-01-15 21:18:10 -05:00
|
|
|
new_owner_user_stat.save!
|
2015-03-11 15:54:11 -04:00
|
|
|
end
|
2013-02-09 10:33:07 -05:00
|
|
|
end
|
|
|
|
|
2014-10-27 17:06:43 -04:00
|
|
|
def self_edit?
|
|
|
|
@editor == @post.user
|
2013-12-10 13:47:07 -05:00
|
|
|
end
|
|
|
|
|
2015-12-29 16:59:26 -05:00
|
|
|
def remove_flags_and_unhide_post
|
2019-06-20 11:23:49 -04:00
|
|
|
return if @opts[:deleting_post]
|
2014-10-27 17:06:43 -04:00
|
|
|
return unless editing_a_flagged_and_hidden_post?
|
2018-04-06 17:37:29 -04:00
|
|
|
|
|
|
|
flaggers = []
|
2017-10-17 13:31:45 -04:00
|
|
|
@post.post_actions.where(post_action_type_id: PostActionType.flag_types_without_custom.values).each do |action|
|
2018-04-06 17:37:29 -04:00
|
|
|
flaggers << action.user if action.user
|
2015-12-29 16:59:26 -05:00
|
|
|
action.remove_act!(Discourse.system_user)
|
|
|
|
end
|
2018-04-06 17:37:29 -04:00
|
|
|
|
2014-10-27 17:06:43 -04:00
|
|
|
@post.unhide!
|
2018-04-06 17:37:29 -04:00
|
|
|
PostActionNotifier.after_post_unhide(@post, flaggers)
|
2014-10-27 17:06:43 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def editing_a_flagged_and_hidden_post?
|
|
|
|
self_edit? &&
|
|
|
|
@post.hidden &&
|
|
|
|
@post.hidden_reason_id == Post.hidden_reasons[:flag_threshold_reached]
|
|
|
|
end
|
2013-02-09 10:33:07 -05:00
|
|
|
|
2014-10-27 17:06:43 -04:00
|
|
|
def update_topic
|
|
|
|
Topic.transaction do
|
2015-01-28 11:01:01 -05:00
|
|
|
PostRevisor.tracked_topic_fields.each do |f, cb|
|
2015-01-27 12:13:45 -05:00
|
|
|
if !@topic_changes.errored? && @fields.has_key?(f)
|
2021-03-02 18:59:23 -05:00
|
|
|
cb.call(@topic_changes, @fields[f], @fields)
|
2015-01-27 12:13:45 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
unless @topic_changes.errored?
|
|
|
|
@topic_changes.check_result(@topic.save(validate: @validate_topic))
|
|
|
|
end
|
2013-02-09 10:33:07 -05:00
|
|
|
end
|
2014-10-27 17:06:43 -04:00
|
|
|
end
|
2013-02-09 10:33:07 -05:00
|
|
|
|
2014-10-27 17:06:43 -04:00
|
|
|
def create_or_update_revision
|
2016-08-19 13:13:22 -04:00
|
|
|
return if @skip_revision
|
2014-11-03 09:31:11 -05:00
|
|
|
# don't create an empty revision if something failed
|
|
|
|
return unless successfully_saved_post_and_topic
|
|
|
|
@version_changed ? create_revision : update_revision
|
2014-10-27 17:06:43 -04:00
|
|
|
end
|
2013-12-31 14:37:43 -05:00
|
|
|
|
2014-10-27 17:06:43 -04:00
|
|
|
def create_revision
|
2015-01-27 12:13:45 -05:00
|
|
|
modifications = post_changes.merge(@topic_changes.diff)
|
2018-03-07 00:44:21 -05:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2020-05-11 04:32:41 -04:00
|
|
|
@post_revision = PostRevision.create!(
|
2014-10-27 17:06:43 -04:00
|
|
|
user_id: @post.last_editor_id,
|
|
|
|
post_id: @post.id,
|
|
|
|
number: @post.version,
|
2019-06-04 15:48:06 -04:00
|
|
|
modifications: modifications,
|
|
|
|
hidden: only_hidden_tags_changed?
|
2014-10-27 17:06:43 -04:00
|
|
|
)
|
2013-02-09 10:33:07 -05:00
|
|
|
end
|
|
|
|
|
2014-10-27 17:06:43 -04:00
|
|
|
def update_revision
|
|
|
|
return unless revision = PostRevision.find_by(post_id: @post.id, number: @post.version)
|
|
|
|
revision.user_id = @post.last_editor_id
|
2015-01-27 12:13:45 -05:00
|
|
|
modifications = post_changes.merge(@topic_changes.diff)
|
2018-03-07 00:44:21 -05:00
|
|
|
|
2015-04-18 07:53:53 -04:00
|
|
|
modifications.each_key do |field|
|
2014-10-27 17:06:43 -04:00
|
|
|
if revision.modifications.has_key?(field)
|
2021-11-09 09:29:37 -05:00
|
|
|
old_value = revision.modifications[field][0]
|
|
|
|
new_value = modifications[field][1]
|
|
|
|
if old_value.to_s != new_value.to_s
|
2015-05-29 14:08:39 -04:00
|
|
|
revision.modifications[field] = [old_value, new_value]
|
|
|
|
else
|
|
|
|
revision.modifications.delete(field)
|
|
|
|
end
|
2014-10-27 17:06:43 -04:00
|
|
|
else
|
|
|
|
revision.modifications[field] = modifications[field]
|
|
|
|
end
|
|
|
|
end
|
2015-05-29 14:08:39 -04:00
|
|
|
# should probably do this before saving the post!
|
|
|
|
if revision.modifications.empty?
|
|
|
|
revision.destroy
|
2021-11-09 09:29:37 -05:00
|
|
|
@post.last_editor_id = PostRevision.where(post_id: @post.id).order(number: :desc).pluck_first(:user_id) || @post.user_id
|
2015-05-29 14:08:39 -04:00
|
|
|
@post.version -= 1
|
|
|
|
@post.public_version -= 1
|
|
|
|
@post.save
|
|
|
|
else
|
|
|
|
revision.save
|
|
|
|
end
|
2014-10-27 17:06:43 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def post_changes
|
|
|
|
@post.previous_changes.slice(*POST_TRACKED_FIELDS)
|
|
|
|
end
|
|
|
|
|
2021-03-24 20:24:50 -04:00
|
|
|
def topic_diff
|
|
|
|
@topic_changes.diff
|
|
|
|
end
|
|
|
|
|
2014-10-27 17:06:43 -04:00
|
|
|
def perform_edit
|
|
|
|
return if bypass_rate_limiter?
|
|
|
|
EditRateLimiter.new(@editor).performed!
|
|
|
|
end
|
|
|
|
|
|
|
|
def bypass_rate_limiter?
|
|
|
|
@opts[:bypass_rate_limiter] == true
|
|
|
|
end
|
|
|
|
|
|
|
|
def bump_topic
|
|
|
|
return if bypass_bump? || !is_last_post?
|
|
|
|
@topic.update_column(:bumped_at, Time.now)
|
2020-04-30 18:33:57 -04:00
|
|
|
TopicTrackingState.publish_muted(@topic)
|
2020-12-10 00:49:05 -05:00
|
|
|
TopicTrackingState.publish_unmuted(@topic)
|
2014-10-27 17:06:43 -04:00
|
|
|
TopicTrackingState.publish_latest(@topic)
|
|
|
|
end
|
|
|
|
|
|
|
|
def bypass_bump?
|
2019-05-06 14:51:51 -04:00
|
|
|
!@post_successfully_saved ||
|
|
|
|
@topic_changes.errored? ||
|
|
|
|
@opts[:bypass_bump] == true ||
|
|
|
|
@post.whisper? ||
|
|
|
|
only_hidden_tags_changed?
|
|
|
|
end
|
|
|
|
|
|
|
|
def only_hidden_tags_changed?
|
2019-10-21 08:27:31 -04:00
|
|
|
return false if (hidden_tag_names = DiscourseTagging.hidden_tag_names).blank?
|
|
|
|
|
2019-05-06 14:51:51 -04:00
|
|
|
modifications = post_changes.merge(@topic_changes.diff)
|
2019-10-21 08:27:31 -04:00
|
|
|
if modifications.keys.size == 1 && (tags_diff = modifications["tags"]).present?
|
2019-05-06 14:51:51 -04:00
|
|
|
a, b = tags_diff[0] || [], tags_diff[1] || []
|
2019-06-07 14:25:42 -04:00
|
|
|
changed_tags = ((a + b) - (a & b)).map(&:presence).compact
|
2019-10-21 08:27:31 -04:00
|
|
|
if (changed_tags - hidden_tag_names).empty?
|
2019-05-06 14:51:51 -04:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
2019-10-21 08:27:31 -04:00
|
|
|
|
2019-05-06 14:51:51 -04:00
|
|
|
false
|
2014-10-27 17:06:43 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def is_last_post?
|
|
|
|
!Post.where(topic_id: @topic.id)
|
|
|
|
.where("post_number > ?", @post.post_number)
|
|
|
|
.exists?
|
|
|
|
end
|
|
|
|
|
|
|
|
def plugin_callbacks
|
|
|
|
DiscourseEvent.trigger(:before_edit_post, @post)
|
|
|
|
DiscourseEvent.trigger(:validate_post, @post)
|
|
|
|
end
|
|
|
|
|
|
|
|
def revise_topic
|
2015-04-23 13:33:29 -04:00
|
|
|
return unless @post.is_first_post?
|
2013-02-21 18:09:56 -05:00
|
|
|
|
2014-10-27 17:06:43 -04:00
|
|
|
update_topic_excerpt
|
|
|
|
update_category_description
|
|
|
|
end
|
|
|
|
|
|
|
|
def update_topic_excerpt
|
2020-05-23 00:56:13 -04:00
|
|
|
@topic.update_excerpt(@post.excerpt_for_topic)
|
2014-10-27 17:06:43 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def update_category_description
|
|
|
|
return unless category = Category.find_by(topic_id: @topic.id)
|
2013-02-21 18:09:56 -05:00
|
|
|
|
2020-05-04 23:46:57 -04:00
|
|
|
doc = Nokogiri::HTML5.fragment(@post.cooked)
|
2016-12-07 17:05:14 -05:00
|
|
|
doc.css("img").remove
|
|
|
|
|
2017-07-21 15:07:29 -04:00
|
|
|
if html = doc.css("p").first&.inner_html&.strip
|
|
|
|
new_description = html unless html.starts_with?(Category.post_template[0..50])
|
|
|
|
category.update_column(:description, new_description)
|
|
|
|
@category_changed = category
|
|
|
|
else
|
2019-04-30 02:58:18 -04:00
|
|
|
@post.errors.add(:base, I18n.t("category.errors.description_incomplete"))
|
2017-07-21 15:07:29 -04:00
|
|
|
end
|
2013-02-21 18:09:56 -05:00
|
|
|
end
|
|
|
|
|
2014-10-27 17:06:43 -04:00
|
|
|
def advance_draft_sequence
|
|
|
|
@post.advance_draft_sequence
|
2014-03-18 13:40:40 -04:00
|
|
|
end
|
|
|
|
|
2013-02-09 10:33:07 -05:00
|
|
|
def post_process_post
|
|
|
|
@post.invalidate_oneboxes = true
|
|
|
|
@post.trigger_post_process
|
2020-05-11 04:32:41 -04:00
|
|
|
DiscourseEvent.trigger(:post_edited, @post, self.topic_changed?, self)
|
2013-02-09 10:33:07 -05:00
|
|
|
end
|
2014-10-27 17:06:43 -04:00
|
|
|
|
|
|
|
def update_topic_word_counts
|
2018-06-19 02:13:14 -04:00
|
|
|
DB.exec("UPDATE topics
|
2014-10-27 17:06:43 -04:00
|
|
|
SET word_count = (
|
|
|
|
SELECT SUM(COALESCE(posts.word_count, 0))
|
|
|
|
FROM posts
|
|
|
|
WHERE posts.topic_id = :topic_id
|
|
|
|
)
|
|
|
|
WHERE topics.id = :topic_id", topic_id: @topic.id)
|
|
|
|
end
|
|
|
|
|
|
|
|
def alert_users
|
2018-01-30 16:21:07 -05:00
|
|
|
return if @editor.id == Discourse::SYSTEM_USER_ID
|
2018-05-24 11:27:43 -04:00
|
|
|
Jobs.enqueue(:post_alert, post_id: @post.id)
|
2014-10-27 17:06:43 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def publish_changes
|
2016-04-07 10:29:01 -04:00
|
|
|
options =
|
|
|
|
if !@topic_changes.diff.empty? && !@topic_changes.errored?
|
|
|
|
{ reload_topic: true }
|
|
|
|
else
|
|
|
|
{}
|
|
|
|
end
|
|
|
|
|
2021-01-20 03:51:31 -05:00
|
|
|
DiscourseEvent.trigger(:before_post_publish_changes, post_changes, @topic_changes, options)
|
|
|
|
|
2016-04-07 10:29:01 -04:00
|
|
|
@post.publish_change_to_clients!(:revised, options)
|
2014-10-27 17:06:43 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def grant_badge
|
|
|
|
BadgeGranter.queue_badge_grant(Badge::Trigger::PostRevision, post: @post)
|
|
|
|
end
|
|
|
|
|
2014-11-03 09:31:11 -05:00
|
|
|
def successfully_saved_post_and_topic
|
2015-01-27 12:13:45 -05:00
|
|
|
@post_successfully_saved && !@topic_changes.errored?
|
2014-11-03 09:31:11 -05:00
|
|
|
end
|
|
|
|
|
2020-10-28 15:47:50 -04:00
|
|
|
def guardian
|
|
|
|
@guardian ||= Guardian.new(@editor)
|
|
|
|
end
|
|
|
|
|
2013-02-09 10:33:07 -05:00
|
|
|
end
|