2019-05-02 18:17:27 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
# Responsible for creating posts and topics
|
|
|
|
#
|
|
|
|
|
|
|
|
class PostCreator
|
2015-03-26 16:57:50 -04:00
|
|
|
include HasErrors
|
2013-02-05 14:16:51 -05:00
|
|
|
|
2022-11-21 11:11:29 -05:00
|
|
|
attr_reader :opts, :post
|
2013-02-05 14:16:51 -05:00
|
|
|
|
|
|
|
# Acceptable options:
|
|
|
|
#
|
|
|
|
# raw - raw text of post
|
2013-02-25 11:42:20 -05:00
|
|
|
# image_sizes - We can pass a list of the sizes of images in the post as a shortcut.
|
2013-03-18 13:55:34 -04:00
|
|
|
# invalidate_oneboxes - Whether to force invalidation of oneboxes in this post
|
2013-05-13 14:06:16 -04:00
|
|
|
# acting_user - The user performing the action might be different than the user
|
|
|
|
# who is the post "author." For example when copying posts to a new
|
|
|
|
# topic.
|
2013-05-20 02:44:06 -04:00
|
|
|
# created_at - Post creation time (optional)
|
2013-07-21 21:40:39 -04:00
|
|
|
# auto_track - Automatically track this topic if needed (default true)
|
2014-05-17 21:33:34 -04:00
|
|
|
# custom_fields - Custom fields to be added to the post, Hash (default nil)
|
2015-01-05 10:52:33 -05:00
|
|
|
# post_type - Whether this is a regular post or moderator post.
|
|
|
|
# no_bump - Do not cause this post to bump the topic.
|
|
|
|
# cooking_options - Options for rendering the text
|
|
|
|
# cook_method - Method of cooking the post.
|
|
|
|
# :regular - Pass through Markdown parser and strip bad HTML
|
|
|
|
# :raw_html - Perform no processing
|
2015-06-05 11:46:21 -04:00
|
|
|
# :raw_email - Imported from an email
|
2015-01-05 10:52:33 -05:00
|
|
|
# via_email - Mark this post as arriving via email
|
|
|
|
# raw_email - Full text of arriving email (to store)
|
2015-07-24 16:39:03 -04:00
|
|
|
# action_code - Describes a small_action post (optional)
|
2016-08-24 11:05:49 -04:00
|
|
|
# skip_jobs - Don't enqueue jobs when creation succeeds. This is needed if you
|
|
|
|
# wrap `PostCreator` in a transaction, as the sidekiq jobs could
|
|
|
|
# dequeue before the commit finishes. If you do this, be sure to
|
2021-05-20 21:43:47 -04:00
|
|
|
# call `enqueue_jobs` after the transaction is committed.
|
2018-07-05 05:07:46 -04:00
|
|
|
# hidden_reason_id - Reason for hiding the post (optional)
|
2019-10-17 01:56:40 -04:00
|
|
|
# skip_validations - Do not validate any of the content in the post
|
2019-11-26 02:23:10 -05:00
|
|
|
# draft_key - the key of the draft we are creating (will be deleted on success)
|
2022-02-09 03:37:38 -05:00
|
|
|
# advance_draft - Destroy draft after creating post or topic
|
2020-12-02 18:43:19 -05:00
|
|
|
# silent - Do not update topic stats and fields like last_post_user_id
|
2013-02-05 14:16:51 -05:00
|
|
|
#
|
|
|
|
# When replying to a topic:
|
|
|
|
# topic_id - topic we're replying to
|
|
|
|
# reply_to_post_number - post number we're replying to
|
|
|
|
#
|
|
|
|
# When creating a topic:
|
|
|
|
# title - New topic title
|
|
|
|
# archetype - Topic archetype
|
2014-09-08 11:11:56 -04:00
|
|
|
# is_warning - Is the topic a warning?
|
2013-02-05 14:16:51 -05:00
|
|
|
# category - Category to assign to topic
|
|
|
|
# target_usernames - comma delimited list of usernames for membership (private message)
|
2013-05-09 03:37:34 -04:00
|
|
|
# target_group_names - comma delimited list of groups for membership (private message)
|
2013-02-05 14:16:51 -05:00
|
|
|
# meta_data - Topic meta data hash
|
2015-01-19 09:00:55 -05:00
|
|
|
# created_at - Topic creation time (optional)
|
|
|
|
# pinned_at - Topic pinned time (optional)
|
2015-03-13 16:24:11 -04:00
|
|
|
# pinned_globally - Is the topic pinned globally (optional)
|
2018-03-13 15:59:12 -04:00
|
|
|
# shared_draft - Is the topic meant to be a shared draft
|
2019-05-25 09:53:03 -04:00
|
|
|
# topic_opts - Options to be overwritten for topic
|
2013-06-21 11:36:33 -04:00
|
|
|
#
|
2013-02-05 14:16:51 -05:00
|
|
|
def initialize(user, opts)
|
2013-04-05 00:29:46 -04:00
|
|
|
# TODO: we should reload user in case it is tainted, should take in a user_id as opposed to user
|
|
|
|
# If we don't do this we introduce a rather risky dependency
|
2013-02-25 11:42:20 -05:00
|
|
|
@user = user
|
2020-11-19 08:09:56 -05:00
|
|
|
@spam = false
|
2013-06-21 11:36:33 -04:00
|
|
|
@opts = opts || {}
|
2020-11-19 08:09:56 -05:00
|
|
|
|
|
|
|
opts[:title] = pg_clean_up(opts[:title]) if opts[:title]&.include?("\u0000")
|
|
|
|
opts[:raw] = pg_clean_up(opts[:raw]) if opts[:raw]&.include?("\u0000")
|
2018-07-05 05:07:46 -04:00
|
|
|
opts[:visible] = false if opts[:visible].nil? && opts[:hidden_reason_id].present?
|
2015-09-22 13:32:19 -04:00
|
|
|
|
2020-11-19 08:09:56 -05:00
|
|
|
opts.delete(:reply_to_post_number) unless opts[:topic_id]
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
2015-08-18 22:27:55 -04:00
|
|
|
def pg_clean_up(str)
|
|
|
|
str.gsub("\u0000", "")
|
2015-08-18 22:15:38 -04:00
|
|
|
end
|
|
|
|
|
2013-05-10 16:58:23 -04:00
|
|
|
def spam?
|
|
|
|
@spam
|
|
|
|
end
|
|
|
|
|
2014-07-30 23:15:16 -04:00
|
|
|
def skip_validations?
|
|
|
|
@opts[:skip_validations]
|
|
|
|
end
|
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
def guardian
|
2020-11-19 08:09:56 -05:00
|
|
|
@guardian ||= @opts[:guardian] || Guardian.new(@user)
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
2015-03-26 16:57:50 -04:00
|
|
|
def valid?
|
2013-06-04 14:13:01 -04:00
|
|
|
@topic = nil
|
|
|
|
@post = nil
|
2013-02-05 14:16:51 -05:00
|
|
|
|
2014-07-30 23:15:16 -04:00
|
|
|
if @user.suspended? && !skip_validations?
|
2019-04-30 02:58:18 -04:00
|
|
|
errors.add(:base, I18n.t(:user_is_suspended))
|
2015-03-26 16:57:50 -04:00
|
|
|
return false
|
2014-07-30 23:15:16 -04:00
|
|
|
end
|
|
|
|
|
2017-07-05 13:09:45 -04:00
|
|
|
if @opts[:target_usernames].present? && !skip_validations? && !@user.staff?
|
2020-06-17 14:26:14 -04:00
|
|
|
names = @opts[:target_usernames].split(",").flatten.map(&:downcase)
|
2017-10-06 02:33:38 -04:00
|
|
|
|
|
|
|
# Make sure max_allowed_message_recipients setting is respected
|
|
|
|
max_allowed_message_recipients = SiteSetting.max_allowed_message_recipients
|
|
|
|
|
|
|
|
if names.length > max_allowed_message_recipients
|
2019-04-30 02:58:18 -04:00
|
|
|
errors.add(
|
|
|
|
:base,
|
2020-01-23 10:37:48 -05:00
|
|
|
I18n.t(:max_pm_recipients, recipients_limit: max_allowed_message_recipients),
|
2017-10-06 02:33:38 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
return false
|
|
|
|
end
|
2017-07-05 13:09:45 -04:00
|
|
|
|
2022-07-12 23:58:01 -04:00
|
|
|
# Make sure none of the users have muted or ignored the creator or prevented
|
|
|
|
# PMs from being sent to them
|
2022-07-14 01:23:09 -04:00
|
|
|
target_users = User.where(username_lower: names.map(&:downcase)).pluck(:id, :username).to_h
|
|
|
|
UserCommScreener
|
|
|
|
.new(acting_user: @user, target_user_ids: target_users.keys)
|
|
|
|
.preventing_actor_communication
|
|
|
|
.each do |user_id|
|
|
|
|
errors.add(:base, I18n.t(:not_accepting_pms, username: target_users[user_id]))
|
2020-07-20 17:23:49 -04:00
|
|
|
end
|
|
|
|
|
2017-10-06 03:56:58 -04:00
|
|
|
return false if errors[:base].present?
|
2017-01-03 14:51:35 -05:00
|
|
|
end
|
|
|
|
|
2015-03-26 16:57:50 -04:00
|
|
|
if new_topic?
|
|
|
|
topic_creator = TopicCreator.new(@user, guardian, @opts)
|
|
|
|
return false unless skip_validations? || validate_child(topic_creator)
|
|
|
|
else
|
|
|
|
@topic = Topic.find_by(id: @opts[:topic_id])
|
2020-02-24 09:55:12 -05:00
|
|
|
|
|
|
|
if @topic.present? && @opts[:archetype] == Archetype.private_message
|
|
|
|
errors.add(:base, I18n.t(:create_pm_on_existing_topic))
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2020-10-28 15:47:50 -04:00
|
|
|
if guardian.affected_by_slow_mode?(@topic)
|
2020-10-16 15:24:38 -04:00
|
|
|
tu = TopicUser.find_by(user: @user, topic: @topic)
|
|
|
|
|
|
|
|
if tu&.last_posted_at
|
|
|
|
threshold = tu.last_posted_at + @topic.slow_mode_seconds.seconds
|
|
|
|
|
|
|
|
if DateTime.now < threshold
|
|
|
|
errors.add(:base, I18n.t(:slow_mode_enabled))
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-02-16 04:40:11 -05:00
|
|
|
if @topic.blank? || !(@opts[:skip_guardian] || guardian.can_create?(Post, @topic))
|
2019-04-30 02:58:18 -04:00
|
|
|
errors.add(:base, I18n.t(:topic_not_found))
|
2015-03-26 16:57:50 -04:00
|
|
|
return false
|
|
|
|
end
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
2015-03-26 16:57:50 -04:00
|
|
|
setup_post
|
|
|
|
|
|
|
|
return true if skip_validations?
|
2016-04-25 17:03:17 -04:00
|
|
|
|
2015-03-26 16:57:50 -04:00
|
|
|
if @post.has_host_spam?
|
|
|
|
@spam = true
|
2019-04-30 02:58:18 -04:00
|
|
|
errors.add(:base, I18n.t(:spamming_host))
|
2015-03-26 16:57:50 -04:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2022-11-21 11:11:29 -05:00
|
|
|
DiscourseEvent.trigger :before_create_post, @post, @opts
|
2015-03-26 16:57:50 -04:00
|
|
|
DiscourseEvent.trigger :validate_post, @post
|
|
|
|
|
2019-10-02 00:01:53 -04:00
|
|
|
post_validator = PostValidator.new(skip_topic: true)
|
2015-03-26 16:57:50 -04:00
|
|
|
post_validator.validate(@post)
|
|
|
|
|
|
|
|
valid = @post.errors.blank?
|
|
|
|
add_errors_from(@post) unless valid
|
|
|
|
valid
|
|
|
|
end
|
|
|
|
|
|
|
|
def create
|
|
|
|
if valid?
|
|
|
|
transaction do
|
2015-08-03 00:29:04 -04:00
|
|
|
build_post_stats
|
2015-03-26 16:57:50 -04:00
|
|
|
create_topic
|
2019-03-08 03:48:35 -05:00
|
|
|
create_post_notice
|
2015-03-26 16:57:50 -04:00
|
|
|
save_post
|
2019-01-03 12:03:01 -05:00
|
|
|
UserActionManager.post_created(@post)
|
2015-03-26 16:57:50 -04:00
|
|
|
extract_links
|
|
|
|
track_topic
|
|
|
|
update_topic_stats
|
|
|
|
update_topic_auto_close
|
|
|
|
update_user_counts
|
|
|
|
create_embedded_topic
|
2020-01-15 22:50:27 -05:00
|
|
|
@post.link_post_uploads
|
2023-02-19 20:21:54 -05:00
|
|
|
@post.update_uploads_secure_status(source: "post creator")
|
2020-07-20 20:00:39 -04:00
|
|
|
delete_owned_bookmarks
|
2015-03-26 16:57:50 -04:00
|
|
|
ensure_in_allowed_users if guardian.is_staff?
|
2020-07-10 05:05:55 -04:00
|
|
|
unarchive_message if !@opts[:import_mode]
|
2022-02-09 03:37:38 -05:00
|
|
|
DraftSequence.next!(@user, draft_key) if !@opts[:import_mode] && @opts[:advance_draft]
|
2015-03-26 16:57:50 -04:00
|
|
|
@post.save_reply_relationships
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-01-04 09:30:17 -05:00
|
|
|
if @post && errors.blank? && !@opts[:import_mode]
|
2019-01-08 02:02:51 -05:00
|
|
|
store_unique_post_key
|
2016-12-02 01:03:31 -05:00
|
|
|
# update counters etc.
|
|
|
|
@post.topic.reload
|
|
|
|
|
2014-07-30 00:04:27 -04:00
|
|
|
publish
|
2014-03-18 00:22:39 -04:00
|
|
|
|
|
|
|
track_latest_on_category
|
2018-09-06 02:08:03 -04:00
|
|
|
enqueue_jobs unless @opts[:skip_jobs]
|
2014-07-22 21:42:24 -04:00
|
|
|
BadgeGranter.queue_badge_grant(Badge::Trigger::PostRevision, post: @post)
|
2015-03-31 12:58:56 -04:00
|
|
|
|
2018-01-03 04:24:01 -05:00
|
|
|
trigger_after_events unless opts[:skip_events]
|
2016-04-11 23:29:48 -04:00
|
|
|
|
2019-01-04 09:30:17 -05:00
|
|
|
auto_close
|
2014-03-18 00:22:39 -04:00
|
|
|
end
|
2014-02-10 14:29:31 -05:00
|
|
|
|
2021-04-21 07:41:36 -04:00
|
|
|
if !opts[:import_mode]
|
|
|
|
handle_spam if (@spam || @post)
|
|
|
|
|
|
|
|
ReviewablePost.queue_for_review_if_possible(@post, @user) if !@spam && @post && errors.blank?
|
|
|
|
end
|
2014-07-30 00:04:27 -04:00
|
|
|
|
2013-06-04 14:13:01 -04:00
|
|
|
@post
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
2016-07-14 23:36:06 -04:00
|
|
|
def create!
|
|
|
|
create
|
|
|
|
|
|
|
|
if !self.errors.full_messages.empty?
|
2018-06-20 09:19:37 -04:00
|
|
|
raise ActiveRecord::RecordNotSaved.new(self.errors.full_messages.to_sentence)
|
2016-07-14 23:36:06 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
@post
|
|
|
|
end
|
|
|
|
|
2018-09-06 02:08:03 -04:00
|
|
|
def enqueue_jobs
|
2016-08-23 16:07:38 -04:00
|
|
|
return unless @post && !@post.errors.present?
|
2017-06-12 03:41:39 -04:00
|
|
|
|
|
|
|
PostJobsEnqueuer.new(
|
|
|
|
@post,
|
|
|
|
@topic,
|
|
|
|
new_topic?,
|
|
|
|
import_mode: @opts[:import_mode],
|
2018-09-06 02:08:03 -04:00
|
|
|
post_alert_options: @opts[:post_alert_options],
|
2017-06-12 03:41:39 -04:00
|
|
|
).enqueue_jobs
|
2016-08-23 16:07:38 -04:00
|
|
|
end
|
|
|
|
|
2018-01-03 04:24:01 -05:00
|
|
|
def trigger_after_events
|
|
|
|
DiscourseEvent.trigger(:topic_created, @post.topic, @opts, @user) unless @opts[:topic_id]
|
|
|
|
DiscourseEvent.trigger(:post_created, @post, @opts, @user)
|
|
|
|
end
|
|
|
|
|
2015-08-03 00:29:04 -04:00
|
|
|
def self.track_post_stats
|
2020-04-30 02:48:34 -04:00
|
|
|
Rails.env != "test" || @track_post_stats
|
2015-08-03 00:29:04 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.track_post_stats=(val)
|
|
|
|
@track_post_stats = val
|
|
|
|
end
|
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
def self.create(user, opts)
|
2013-02-25 11:42:20 -05:00
|
|
|
PostCreator.new(user, opts).create
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
2016-07-14 23:36:06 -04:00
|
|
|
def self.create!(user, opts)
|
|
|
|
PostCreator.new(user, opts).create!
|
|
|
|
end
|
|
|
|
|
2013-06-09 12:48:44 -04:00
|
|
|
def self.before_create_tasks(post)
|
2015-09-25 11:28:41 -04:00
|
|
|
set_reply_info(post)
|
2013-06-09 12:48:44 -04:00
|
|
|
|
2016-01-11 05:16:23 -05:00
|
|
|
post.word_count = post.raw.scan(/[[:word:]]+/).size
|
2016-12-02 01:03:31 -05:00
|
|
|
|
|
|
|
whisper = post.post_type == Post.types[:whisper]
|
2019-03-08 03:49:34 -05:00
|
|
|
increase_posts_count =
|
|
|
|
!post.topic&.private_message? || post.post_type != Post.types[:small_action]
|
|
|
|
post.post_number ||=
|
|
|
|
Topic.next_post_number(
|
|
|
|
post.topic_id,
|
|
|
|
reply: post.reply_to_post_number.present?,
|
|
|
|
whisper: whisper,
|
|
|
|
post: increase_posts_count,
|
|
|
|
)
|
2013-06-21 11:36:33 -04:00
|
|
|
|
|
|
|
cooking_options = post.cooking_options || {}
|
|
|
|
cooking_options[:topic_id] = post.topic_id
|
|
|
|
|
2015-09-29 12:51:26 -04:00
|
|
|
post.cooked ||= post.cook(post.raw, cooking_options.symbolize_keys)
|
2013-06-09 12:48:44 -04:00
|
|
|
post.sort_order = post.post_number
|
|
|
|
post.last_version_at ||= Time.now
|
|
|
|
end
|
|
|
|
|
2015-09-25 11:28:41 -04:00
|
|
|
def self.set_reply_info(post)
|
2014-02-10 14:29:31 -05:00
|
|
|
return unless post.reply_to_post_number.present?
|
|
|
|
|
2019-06-04 21:29:27 -04:00
|
|
|
# Before the locking here was added, replying to a post and liking a post
|
|
|
|
# at roughly the same time could cause a deadlock.
|
|
|
|
#
|
|
|
|
# Liking a post grabs an update lock on the post and then on the topic (to
|
|
|
|
# update like counts).
|
|
|
|
#
|
|
|
|
# Here, we lock the replied to post before getting the topic lock so that
|
|
|
|
# we can update the replied to post later without causing a deadlock.
|
|
|
|
|
2015-09-25 11:28:41 -04:00
|
|
|
reply_info =
|
|
|
|
Post
|
|
|
|
.where(topic_id: post.topic_id, post_number: post.reply_to_post_number)
|
|
|
|
.select(:user_id, :post_type)
|
2019-06-04 21:29:27 -04:00
|
|
|
.lock
|
2015-09-25 11:28:41 -04:00
|
|
|
.first
|
|
|
|
|
|
|
|
if reply_info.present?
|
|
|
|
post.reply_to_user_id ||= reply_info.user_id
|
|
|
|
whisper_type = Post.types[:whisper]
|
|
|
|
post.post_type = whisper_type if reply_info.post_type == whisper_type
|
|
|
|
end
|
2014-02-10 14:29:31 -05:00
|
|
|
end
|
2013-06-09 12:48:44 -04:00
|
|
|
|
2013-05-02 01:15:17 -04:00
|
|
|
protected
|
|
|
|
|
2019-11-26 02:23:10 -05:00
|
|
|
def draft_key
|
|
|
|
@draft_key ||= @opts[:draft_key]
|
2020-05-11 23:13:18 -04:00
|
|
|
@draft_key ||= @topic ? @topic.draft_key : Draft::NEW_TOPIC
|
2019-11-26 02:23:10 -05:00
|
|
|
end
|
|
|
|
|
2015-08-03 00:29:04 -04:00
|
|
|
def build_post_stats
|
|
|
|
if PostCreator.track_post_stats
|
|
|
|
sequence = DraftSequence.current(@user, draft_key)
|
|
|
|
revisions =
|
2023-02-12 23:39:45 -05:00
|
|
|
Draft.where(sequence: sequence, user_id: @user.id, draft_key: draft_key).pick(:revisions) ||
|
|
|
|
0
|
2015-08-03 00:29:04 -04:00
|
|
|
|
|
|
|
@post.build_post_stat(
|
|
|
|
drafts_saved: revisions,
|
|
|
|
typing_duration_msecs: @opts[:typing_duration_msecs] || 0,
|
|
|
|
composer_open_duration_msecs: @opts[:composer_open_duration_msecs] || 0,
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-04-11 23:29:48 -04:00
|
|
|
def auto_close
|
2018-04-23 01:32:08 -04:00
|
|
|
topic = @post.topic
|
|
|
|
is_private_message = topic.private_message?
|
|
|
|
topic_posts_count = @post.topic.posts_count
|
2016-04-11 23:29:48 -04:00
|
|
|
|
2018-04-23 01:32:08 -04:00
|
|
|
if is_private_message && !topic.closed && SiteSetting.auto_close_messages_post_count > 0 &&
|
|
|
|
SiteSetting.auto_close_messages_post_count <= topic_posts_count
|
|
|
|
@post.topic.update_status(
|
|
|
|
:closed,
|
|
|
|
true,
|
|
|
|
Discourse.system_user,
|
|
|
|
message:
|
|
|
|
I18n.t(
|
|
|
|
"topic_statuses.autoclosed_message_max_posts",
|
2020-08-17 10:40:47 -04:00
|
|
|
count: SiteSetting.auto_close_messages_post_count,
|
|
|
|
locale: SiteSetting.default_locale,
|
2018-04-23 01:32:08 -04:00
|
|
|
),
|
|
|
|
)
|
2016-04-11 23:29:48 -04:00
|
|
|
elsif !is_private_message && !topic.closed && SiteSetting.auto_close_topics_post_count > 0 &&
|
2018-04-23 01:32:08 -04:00
|
|
|
SiteSetting.auto_close_topics_post_count <= topic_posts_count
|
|
|
|
topic.update_status(
|
|
|
|
:closed,
|
|
|
|
true,
|
|
|
|
Discourse.system_user,
|
|
|
|
message:
|
|
|
|
I18n.t(
|
|
|
|
"topic_statuses.autoclosed_topic_max_posts",
|
2020-08-17 10:40:47 -04:00
|
|
|
count: SiteSetting.auto_close_topics_post_count,
|
|
|
|
locale: SiteSetting.default_locale,
|
2018-04-23 01:32:08 -04:00
|
|
|
),
|
|
|
|
)
|
2020-11-02 01:48:48 -05:00
|
|
|
|
|
|
|
if SiteSetting.auto_close_topics_create_linked_topic?
|
|
|
|
# enqueue a job to create a linked topic
|
|
|
|
Jobs.enqueue_in(5.seconds, :create_linked_topic, post_id: @post.id)
|
|
|
|
end
|
2016-04-11 23:29:48 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-07-30 00:04:27 -04:00
|
|
|
def transaction(&blk)
|
2018-08-23 01:10:55 -04:00
|
|
|
if new_topic?
|
|
|
|
Post.transaction { blk.call }
|
|
|
|
else
|
|
|
|
# we need to ensure post_number is monotonically increasing with no gaps
|
|
|
|
# so we serialize creation to avoid needing rollbacks
|
|
|
|
DistributedMutex.synchronize("topic_id_#{@opts[:topic_id]}") { Post.transaction { blk.call } }
|
2014-07-30 00:04:27 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-04-03 14:42:26 -04:00
|
|
|
# You can supply an `embed_url` for a post to set up the embedded relationship.
|
|
|
|
# This is used by the wp-discourse plugin to associate a remote post with a
|
|
|
|
# discourse post.
|
|
|
|
def create_embedded_topic
|
|
|
|
return unless @opts[:embed_url].present?
|
2020-05-23 00:56:13 -04:00
|
|
|
|
|
|
|
original_uri = URI.parse(@opts[:embed_url])
|
|
|
|
raise Discourse::InvalidParameters.new(:embed_url) unless original_uri.is_a?(URI::HTTP)
|
|
|
|
|
2015-06-15 12:08:55 -04:00
|
|
|
embed =
|
|
|
|
TopicEmbed.new(topic_id: @post.topic_id, post_id: @post.id, embed_url: @opts[:embed_url])
|
|
|
|
rollback_from_errors!(embed) unless embed.save
|
2014-04-03 14:42:26 -04:00
|
|
|
end
|
|
|
|
|
2020-07-20 20:00:39 -04:00
|
|
|
def delete_owned_bookmarks
|
|
|
|
return if !@post.topic_id
|
2020-07-28 19:43:32 -04:00
|
|
|
BookmarkManager.new(@user).destroy_for_topic(
|
|
|
|
Topic.with_deleted.find(@post.topic_id),
|
|
|
|
{ auto_delete_preference: Bookmark.auto_delete_preferences[:on_owner_reply] },
|
|
|
|
@opts,
|
|
|
|
)
|
2020-07-20 20:00:39 -04:00
|
|
|
end
|
|
|
|
|
2014-02-10 14:29:31 -05:00
|
|
|
def handle_spam
|
|
|
|
if @spam
|
|
|
|
GroupMessage.create(
|
|
|
|
Group[:moderators].name,
|
|
|
|
:spam_post_blocked,
|
|
|
|
user: @user,
|
|
|
|
limit_once_per: 24.hours,
|
|
|
|
message_params: {
|
|
|
|
domains: @post.linked_hosts.keys.join(", "),
|
|
|
|
},
|
|
|
|
)
|
2015-03-26 16:57:50 -04:00
|
|
|
elsif @post && errors.blank? && !skip_validations?
|
2019-02-07 13:46:05 -05:00
|
|
|
SpamRule::FlagSockpuppets.new(@post).perform
|
2013-10-17 02:44:56 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-02-10 14:29:31 -05:00
|
|
|
def track_latest_on_category
|
|
|
|
return unless @post && @post.errors.count == 0 && @topic && @topic.category_id
|
|
|
|
|
2022-12-07 14:35:13 -05:00
|
|
|
if @post.is_first_post?
|
|
|
|
Category.where(id: @topic.category_id).update_all(
|
|
|
|
latest_topic_id: @topic.id,
|
|
|
|
latest_post_id: @post.id,
|
|
|
|
)
|
|
|
|
else
|
|
|
|
Category.where(id: @topic.category_id).update_all(latest_post_id: @post.id)
|
|
|
|
end
|
2014-02-10 14:29:31 -05:00
|
|
|
end
|
|
|
|
|
2013-09-06 00:07:23 -04:00
|
|
|
def ensure_in_allowed_users
|
2016-01-13 18:38:04 -05:00
|
|
|
return unless @topic.private_message? && @topic.id
|
2022-08-29 06:01:16 -04:00
|
|
|
return if @post.whisper? || @post.post_type == Post.types[:small_action]
|
2021-07-22 23:35:01 -04:00
|
|
|
return if @topic.topic_allowed_users.exists?(user_id: @user.id)
|
|
|
|
|
|
|
|
if @topic
|
|
|
|
.topic_allowed_groups
|
|
|
|
.where("group_id IN (SELECT group_id FROM group_users where user_id = ?)", @user.id)
|
|
|
|
.exists?
|
2023-01-09 07:10:19 -05:00
|
|
|
return
|
|
|
|
end
|
2013-09-06 00:07:23 -04:00
|
|
|
|
2021-07-22 23:35:01 -04:00
|
|
|
@topic.topic_allowed_users.create!(user_id: @user.id)
|
2013-09-06 00:07:23 -04:00
|
|
|
end
|
|
|
|
|
2015-12-22 19:09:17 -05:00
|
|
|
def unarchive_message
|
|
|
|
return unless @topic.private_message? && @topic.id
|
|
|
|
|
2016-02-07 07:39:07 -05:00
|
|
|
UserArchivedMessage
|
|
|
|
.where(topic_id: @topic.id)
|
|
|
|
.pluck(:user_id)
|
2018-03-06 01:38:43 -05:00
|
|
|
.each { |user_id| UserArchivedMessage.move_to_inbox!(user_id, @topic) }
|
2016-02-07 07:39:07 -05:00
|
|
|
|
|
|
|
GroupArchivedMessage
|
|
|
|
.where(topic_id: @topic.id)
|
|
|
|
.pluck(:group_id)
|
|
|
|
.each do |group_id|
|
2021-09-09 21:20:50 -04:00
|
|
|
GroupArchivedMessage.move_to_inbox!(group_id, @topic, acting_user_id: @user.id)
|
2016-02-07 07:39:07 -05:00
|
|
|
end
|
2015-12-22 19:09:17 -05:00
|
|
|
end
|
|
|
|
|
2013-06-04 14:13:01 -04:00
|
|
|
private
|
|
|
|
|
2015-03-26 16:57:50 -04:00
|
|
|
def create_topic
|
|
|
|
return if @topic
|
|
|
|
begin
|
2019-05-25 09:53:03 -04:00
|
|
|
opts = @opts[:topic_opts] ? @opts.merge(@opts[:topic_opts]) : @opts
|
|
|
|
topic_creator = TopicCreator.new(@user, guardian, opts)
|
2015-03-26 16:57:50 -04:00
|
|
|
@topic = topic_creator.create
|
|
|
|
rescue ActiveRecord::Rollback
|
2016-05-05 08:58:25 -04:00
|
|
|
rollback_from_errors!(topic_creator)
|
2013-05-02 01:15:17 -04:00
|
|
|
end
|
2015-03-26 16:57:50 -04:00
|
|
|
@post.topic_id = @topic.id
|
|
|
|
@post.topic = @topic
|
2016-12-18 08:38:55 -05:00
|
|
|
@post.wiki = true if @topic && @topic.category && @topic.category.all_topics_wiki
|
2013-05-02 01:15:17 -04:00
|
|
|
end
|
|
|
|
|
2013-07-22 01:06:53 -04:00
|
|
|
def update_topic_stats
|
2019-03-28 02:28:01 -04:00
|
|
|
attrs = { updated_at: Time.now }
|
|
|
|
|
2020-12-02 18:43:19 -05:00
|
|
|
if @post.post_type != Post.types[:whisper] && !@opts[:silent]
|
2017-05-25 15:07:12 -04:00
|
|
|
attrs[:last_posted_at] = @post.created_at
|
|
|
|
attrs[:last_post_user_id] = @post.user_id
|
|
|
|
attrs[:word_count] = (@topic.word_count || 0) + @post.word_count
|
2018-04-17 15:08:13 -04:00
|
|
|
attrs[:excerpt] = @post.excerpt_for_topic if new_topic?
|
2017-05-25 15:07:12 -04:00
|
|
|
attrs[:bumped_at] = @post.created_at unless @post.no_bump
|
|
|
|
end
|
2019-03-28 02:28:01 -04:00
|
|
|
|
|
|
|
@topic.update_columns(attrs)
|
2013-07-22 01:06:53 -04:00
|
|
|
end
|
|
|
|
|
2014-10-10 12:21:44 -04:00
|
|
|
def update_topic_auto_close
|
2019-01-04 09:30:17 -05:00
|
|
|
return if @opts[:import_mode]
|
|
|
|
|
2018-04-23 01:32:08 -04:00
|
|
|
if @topic.closed?
|
|
|
|
@topic.delete_topic_timer(TopicTimer.types[:close])
|
|
|
|
else
|
|
|
|
topic_timer = @topic.public_topic_timer
|
2017-03-21 23:12:02 -04:00
|
|
|
|
2021-02-04 19:12:56 -05:00
|
|
|
if topic_timer && topic_timer.based_on_last_post && topic_timer.duration_minutes.to_i > 0
|
2018-04-23 01:32:08 -04:00
|
|
|
@topic.set_or_create_timer(
|
|
|
|
TopicTimer.types[:close],
|
2020-03-19 12:15:05 -04:00
|
|
|
nil,
|
|
|
|
based_on_last_post: topic_timer.based_on_last_post,
|
2021-02-04 19:12:56 -05:00
|
|
|
duration_minutes: topic_timer.duration_minutes,
|
2018-04-23 01:32:08 -04:00
|
|
|
)
|
|
|
|
end
|
2014-10-10 12:21:44 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-06-04 14:13:01 -04:00
|
|
|
def setup_post
|
2023-01-04 17:09:17 -05:00
|
|
|
@opts[:raw] = TextCleaner.normalize_whitespaces(@opts[:raw] || "").rstrip
|
2023-01-09 07:10:19 -05:00
|
|
|
|
2015-03-26 16:57:50 -04:00
|
|
|
post =
|
|
|
|
Post.new(
|
|
|
|
raw: @opts[:raw],
|
|
|
|
topic_id: @topic.try(:id),
|
|
|
|
user: @user,
|
|
|
|
reply_to_post_number: @opts[:reply_to_post_number],
|
|
|
|
)
|
2013-06-04 14:13:01 -04:00
|
|
|
|
2013-06-21 11:36:33 -04:00
|
|
|
# Attributes we pass through to the post instance if present
|
2023-01-09 07:10:19 -05:00
|
|
|
%i[
|
2015-07-24 16:39:03 -04:00
|
|
|
post_type
|
|
|
|
no_bump
|
|
|
|
cooking_options
|
|
|
|
image_sizes
|
|
|
|
acting_user
|
|
|
|
invalidate_oneboxes
|
|
|
|
cook_method
|
|
|
|
via_email
|
|
|
|
raw_email
|
|
|
|
action_code
|
2019-05-06 21:27:05 -04:00
|
|
|
].each { |a| post.public_send("#{a}=", @opts[a]) if @opts[a].present? }
|
2013-06-21 11:36:33 -04:00
|
|
|
|
2013-06-04 14:13:01 -04:00
|
|
|
post.extract_quoted_post_numbers
|
2020-03-09 12:38:13 -04:00
|
|
|
|
|
|
|
post.created_at =
|
|
|
|
if @opts[:created_at].is_a?(Time)
|
|
|
|
@opts[:created_at]
|
|
|
|
elsif @opts[:created_at].present?
|
|
|
|
Time.zone.parse(@opts[:created_at].to_s)
|
|
|
|
end
|
2014-02-10 14:29:31 -05:00
|
|
|
|
2014-05-17 21:33:34 -04:00
|
|
|
if fields = @opts[:custom_fields]
|
|
|
|
post.custom_fields = fields
|
|
|
|
end
|
|
|
|
|
2018-07-05 05:07:46 -04:00
|
|
|
if @opts[:hidden_reason_id].present?
|
|
|
|
post.hidden = true
|
|
|
|
post.hidden_at = Time.zone.now
|
|
|
|
post.hidden_reason_id = @opts[:hidden_reason_id]
|
|
|
|
end
|
|
|
|
|
2013-06-04 14:13:01 -04:00
|
|
|
@post = post
|
|
|
|
end
|
|
|
|
|
|
|
|
def save_post
|
2015-04-21 13:16:05 -04:00
|
|
|
@post.disable_rate_limits! if skip_validations?
|
2019-01-02 09:24:13 -05:00
|
|
|
@post.skip_validation = skip_validations?
|
|
|
|
saved = @post.save
|
2015-03-26 16:57:50 -04:00
|
|
|
rollback_from_errors!(@post) unless saved
|
2013-05-02 01:15:17 -04:00
|
|
|
end
|
2013-06-04 14:13:01 -04:00
|
|
|
|
|
|
|
def store_unique_post_key
|
2013-09-09 16:17:31 -04:00
|
|
|
@post.store_unique_post_key
|
2013-06-04 14:13:01 -04:00
|
|
|
end
|
|
|
|
|
2014-07-28 13:17:37 -04:00
|
|
|
def update_user_counts
|
2016-05-17 13:22:19 -04:00
|
|
|
return if @opts[:import_mode]
|
|
|
|
|
2014-07-28 13:17:37 -04:00
|
|
|
@user.create_user_stat if @user.user_stat.nil?
|
2014-02-10 14:29:31 -05:00
|
|
|
|
2014-07-28 13:17:37 -04:00
|
|
|
if @user.user_stat.first_post_created_at.nil?
|
2022-02-06 22:23:34 -05:00
|
|
|
@user.user_stat.update!(first_post_created_at: @post.created_at)
|
2014-07-28 13:17:37 -04:00
|
|
|
end
|
|
|
|
|
2022-02-10 20:00:58 -05:00
|
|
|
UserStatCountUpdater.increment!(@post) if !@post.hidden || @post.topic.visible
|
2014-07-28 13:17:37 -04:00
|
|
|
|
2019-10-30 18:01:26 -04:00
|
|
|
if !@topic.private_message? && @post.post_type != Post.types[:whisper]
|
|
|
|
@user.update(last_posted_at: @post.created_at)
|
|
|
|
end
|
2013-06-04 14:13:01 -04:00
|
|
|
end
|
|
|
|
|
2019-03-08 03:48:35 -05:00
|
|
|
def create_post_notice
|
2019-04-19 10:53:58 -04:00
|
|
|
return if @opts[:import_mode] || @user.anonymous? || @user.bot? || @user.staged
|
2019-03-11 05:19:58 -04:00
|
|
|
|
2020-01-30 07:24:56 -05:00
|
|
|
last_post_time =
|
|
|
|
Post.where(user_id: @user.id).order(created_at: :desc).limit(1).pluck(:created_at).first
|
2019-03-08 03:48:35 -05:00
|
|
|
|
|
|
|
if !last_post_time
|
2020-11-11 07:49:53 -05:00
|
|
|
@post.custom_fields[Post::NOTICE] = { type: Post.notices[:new_user] }
|
2019-03-08 03:48:35 -05:00
|
|
|
elsif SiteSetting.returning_users_days > 0 &&
|
|
|
|
last_post_time < SiteSetting.returning_users_days.days.ago
|
2020-11-11 07:49:53 -05:00
|
|
|
@post.custom_fields[Post::NOTICE] = {
|
|
|
|
type: Post.notices[:returning_user],
|
|
|
|
last_posted_at: last_post_time.iso8601,
|
|
|
|
}
|
2019-03-08 03:48:35 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-06-04 14:13:01 -04:00
|
|
|
def publish
|
2019-01-04 09:30:17 -05:00
|
|
|
return if @opts[:import_mode] || @post.post_number == 1
|
2022-06-27 17:21:05 -04:00
|
|
|
@post.publish_change_to_clients! :created, { skip_topic_stats: @post.post_number == 1 }
|
2013-06-04 14:13:01 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def extract_links
|
|
|
|
TopicLink.extract_from(@post)
|
2014-07-15 03:47:24 -04:00
|
|
|
QuotedPost.extract_from(@post)
|
2013-06-04 14:13:01 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def track_topic
|
2019-01-04 09:30:17 -05:00
|
|
|
return if @opts[:import_mode] || @opts[:auto_track] == false
|
2014-02-10 14:29:31 -05:00
|
|
|
|
2019-04-11 19:03:07 -04:00
|
|
|
TopicUser.change(
|
|
|
|
@post.user_id,
|
|
|
|
@topic.id,
|
|
|
|
posted: true,
|
|
|
|
last_read_post_number: @post.post_number,
|
2020-10-16 15:24:38 -04:00
|
|
|
last_posted_at: Time.zone.now,
|
|
|
|
)
|
2019-04-11 19:03:07 -04:00
|
|
|
|
|
|
|
# assume it took us 5 seconds of reading time to make a post
|
|
|
|
PostTiming.record_timing(
|
|
|
|
topic_id: @post.topic_id,
|
|
|
|
user_id: @post.user_id,
|
|
|
|
post_number: @post.post_number,
|
|
|
|
msecs: 5000,
|
|
|
|
)
|
2014-06-03 21:41:42 -04:00
|
|
|
|
2015-11-18 16:24:46 -05:00
|
|
|
if @user.staged
|
2016-09-30 12:36:43 -04:00
|
|
|
TopicUser.auto_notification_for_staging(
|
|
|
|
@user.id,
|
|
|
|
@topic.id,
|
|
|
|
TopicUser.notification_reasons[:auto_watch],
|
|
|
|
)
|
2019-01-04 09:30:17 -05:00
|
|
|
elsif !@topic.private_message?
|
2017-10-06 10:37:28 -04:00
|
|
|
notification_level =
|
|
|
|
@user.user_option.notification_level_when_replying ||
|
|
|
|
NotificationLevels.topic_levels[:tracking]
|
|
|
|
TopicUser.auto_notification(
|
|
|
|
@user.id,
|
|
|
|
@topic.id,
|
|
|
|
TopicUser.notification_reasons[:created_post],
|
|
|
|
notification_level,
|
|
|
|
)
|
2015-11-18 16:24:46 -05:00
|
|
|
end
|
2013-06-04 14:13:01 -04:00
|
|
|
end
|
|
|
|
|
2014-02-17 01:57:37 -05:00
|
|
|
def new_topic?
|
|
|
|
@opts[:topic_id].blank?
|
2013-06-04 14:13:01 -04:00
|
|
|
end
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|