discourse/lib/post_creator.rb

245 lines
6.8 KiB
Ruby

# Responsible for creating posts and topics
#
require_dependency 'rate_limiter'
require_dependency 'topic_creator'
class PostCreator
attr_reader :errors, :opts
def self.create(user,opts)
self.new(user,opts).create
end
# Acceptable options:
#
# raw - raw text of post
# image_sizes - We can pass a list of the sizes of images in the post as a shortcut.
# invalidate_oneboxes - Whether to force invalidation of oneboxes in this post
# 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.
# created_at - Post creation time (optional)
#
# 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
# category - Category to assign to topic
# target_usernames - comma delimited list of usernames for membership (private message)
# target_group_names - comma delimited list of groups for membership (private message)
# meta_data - Topic meta data hash
def initialize(user, opts)
# 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
@user = user
@opts = opts
@spam = false
raise Discourse::InvalidParameters.new(:raw) if @opts[:raw].blank?
end
# True if the post was considered spam
def spam?
@spam
end
def guardian
@guardian ||= Guardian.new(@user)
end
def create
@topic = nil
@post = nil
@new_topic = false
Post.transaction do
setup_topic
setup_post
rollback_if_host_spam_detected
save_post
extract_links
store_unique_post_key
send_notifications_for_private_message
track_topic
update_user_counts
publish
@post.advance_draft_sequence
@post.save_reply_relationships
end
enqueue_jobs
@post
end
# Shortcut
def self.create(user, opts)
PostCreator.new(user, opts).create
end
protected
def secure_group_ids(topic)
@secure_group_ids ||= if topic.category && topic.category.secure?
topic.category.secure_group_ids
end
end
def after_post_create
if @post.post_number > 1
TopicTrackingState.publish_unread(@post)
end
end
def after_topic_create
# Don't publish invisible topics
return unless @topic.visible?
return if @topic.private_message?
@topic.posters = @topic.posters_summary
@topic.posts_count = 1
TopicTrackingState.publish_new(@topic)
end
def clear_possible_flags(topic)
# at this point we know the topic is a PM and has been replied to ... check if we need to clear any flags
#
first_post = Post.select(:id).where(topic_id: topic.id).where('post_number = 1').first
post_action = nil
if first_post
post_action = PostAction.where(
related_post_id: first_post.id,
deleted_at: nil,
post_action_type_id: PostActionType.types[:notify_moderators]
).first
end
if post_action
post_action.remove_act!(@user)
end
end
private
def setup_topic
if @opts[:topic_id].blank?
topic_creator = TopicCreator.new(@user, guardian, @opts)
topic = topic_creator.create
@errors = topic_creator.errors
@new_topic = true
else
topic = Topic.where(id: @opts[:topic_id]).first
guardian.ensure_can_create!(Post, topic)
end
@topic = topic
end
def setup_post
post = @topic.posts.new(raw: @opts[:raw],
user: @user,
reply_to_post_number: @opts[:reply_to_post_number])
post.post_type = @opts[:post_type] if @opts[:post_type].present?
post.no_bump = @opts[:no_bump] if @opts[:no_bump].present?
post.extract_quoted_post_numbers
post.acting_user = @opts[:acting_user] if @opts[:acting_user].present?
post.created_at = Time.zone.parse(@opts[:created_at].to_s) if @opts[:created_at].present?
post.image_sizes = @opts[:image_sizes] if @opts[:image_sizes].present?
post.invalidate_oneboxes = @opts[:invalidate_oneboxes] if @opts[:invalidate_oneboxes].present?
@post = post
end
def rollback_if_host_spam_detected
if @post.has_host_spam?
@post.errors.add(:base, I18n.t(:spamming_host))
@errors = @post.errors
@spam = true
raise ActiveRecord::Rollback.new
end
end
def save_post
unless @post.save
@errors = @post.errors
raise ActiveRecord::Rollback.new
end
end
def store_unique_post_key
if SiteSetting.unique_posts_mins > 0
$redis.setex(@post.unique_post_key, SiteSetting.unique_posts_mins.minutes.to_i, "1")
end
end
def send_notifications_for_private_message
# send a mail to notify users in case of a private message
if @topic.private_message?
@topic.allowed_users.where(["users.email_private_messages = true and users.id != ?", @user.id]).each do |u|
Jobs.enqueue_in(SiteSetting.email_time_window_mins.minutes,
:user_email,
type: :private_message,
user_id: u.id,
post_id: @post.id
)
end
clear_possible_flags(@topic) if @post.post_number > 1 && @topic.user_id != @post.user_id
end
end
def update_user_counts
# We don't count replies to your own topics
if @user.id != @topic.user_id
@user.update_topic_reply_count
end
@user.last_posted_at = @post.created_at
@user.save!
end
def publish
if @post.post_number > 1
MessageBus.publish("/topic/#{@post.topic_id}",{
id: @post.id,
created_at: @post.created_at,
user: BasicUserSerializer.new(@post.user).as_json(root: false),
post_number: @post.post_number
},
group_ids: secure_group_ids(@topic)
)
end
end
def extract_links
TopicLink.extract_from(@post)
end
def track_topic
TopicUser.auto_track(@user.id, @topic.id, TopicUser.notification_reasons[:created_post])
end
def enqueue_jobs
if @post && !@post.errors.present?
# We need to enqueue jobs after the transaction. Otherwise they might begin before the data has
# been comitted.
topic_id = @opts[:topic_id] || @topic.try(:id)
Jobs.enqueue(:feature_topic_users, topic_id: @topic.id) if topic_id.present?
@post.trigger_post_process
after_post_create
after_topic_create if @new_topic
end
end
end