2013-02-05 14:16:51 -05:00
|
|
|
# Responsible for creating posts and topics
|
|
|
|
#
|
|
|
|
require_dependency 'rate_limiter'
|
2013-06-04 14:13:01 -04:00
|
|
|
require_dependency 'topic_creator'
|
2013-02-05 14:16:51 -05:00
|
|
|
|
|
|
|
class PostCreator
|
|
|
|
|
2013-03-18 13:55:34 -04:00
|
|
|
attr_reader :errors, :opts
|
2013-02-05 14:16:51 -05:00
|
|
|
|
2013-05-20 02:44:06 -04:00
|
|
|
def self.create(user,opts)
|
|
|
|
self.new(user,opts).create
|
|
|
|
end
|
|
|
|
|
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)
|
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
|
|
|
|
# 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
|
2013-06-21 11:36:33 -04:00
|
|
|
# cooking_options - Options for rendering the text
|
|
|
|
#
|
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
|
2013-06-21 11:36:33 -04:00
|
|
|
@opts = opts || {}
|
2013-05-10 16:58:23 -04:00
|
|
|
@spam = false
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
2013-05-10 16:58:23 -04:00
|
|
|
# True if the post was considered spam
|
|
|
|
def spam?
|
|
|
|
@spam
|
|
|
|
end
|
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
def guardian
|
|
|
|
@guardian ||= Guardian.new(@user)
|
|
|
|
end
|
|
|
|
|
|
|
|
def create
|
2013-06-04 14:13:01 -04:00
|
|
|
@topic = nil
|
|
|
|
@post = nil
|
|
|
|
@new_topic = false
|
2013-02-05 14:16:51 -05:00
|
|
|
|
|
|
|
Post.transaction do
|
2013-06-04 14:13:01 -04:00
|
|
|
setup_topic
|
|
|
|
setup_post
|
|
|
|
rollback_if_host_spam_detected
|
|
|
|
save_post
|
|
|
|
extract_links
|
|
|
|
store_unique_post_key
|
2013-08-07 13:02:49 -04:00
|
|
|
consider_clearing_flags
|
2013-06-04 14:13:01 -04:00
|
|
|
track_topic
|
2013-07-22 01:06:53 -04:00
|
|
|
update_topic_stats
|
2013-06-04 14:13:01 -04:00
|
|
|
update_user_counts
|
|
|
|
publish
|
|
|
|
@post.advance_draft_sequence
|
|
|
|
@post.save_reply_relationships
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
2013-06-10 13:17:09 -04:00
|
|
|
if @spam
|
|
|
|
GroupMessage.create( Group[:moderators].name, :spam_post_blocked, {user: @user, limit_once_per: 24.hours} )
|
|
|
|
end
|
|
|
|
|
2013-06-04 14:13:01 -04:00
|
|
|
enqueue_jobs
|
|
|
|
@post
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
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
|
|
|
|
|
2013-06-09 12:48:44 -04:00
|
|
|
def self.before_create_tasks(post)
|
|
|
|
if post.reply_to_post_number.present?
|
|
|
|
post.reply_to_user_id ||= Post.select(:user_id).where(topic_id: post.topic_id, post_number: post.reply_to_post_number).first.try(:user_id)
|
|
|
|
end
|
|
|
|
|
|
|
|
post.post_number ||= Topic.next_post_number(post.topic_id, post.reply_to_post_number.present?)
|
2013-06-21 11:36:33 -04:00
|
|
|
|
|
|
|
cooking_options = post.cooking_options || {}
|
|
|
|
cooking_options[:topic_id] = post.topic_id
|
|
|
|
|
|
|
|
post.cooked ||= post.cook(post.raw, cooking_options)
|
2013-06-09 12:48:44 -04:00
|
|
|
post.sort_order = post.post_number
|
|
|
|
DiscourseEvent.trigger(:before_create_post, post)
|
|
|
|
post.last_version_at ||= Time.now
|
|
|
|
end
|
|
|
|
|
|
|
|
|
2013-05-02 01:15:17 -04:00
|
|
|
protected
|
|
|
|
|
2013-05-16 01:03:03 -04:00
|
|
|
def secure_group_ids(topic)
|
2013-07-13 21:24:16 -04:00
|
|
|
@secure_group_ids ||= if topic.category && topic.category.read_restricted?
|
2013-05-29 04:11:04 -04:00
|
|
|
topic.category.secure_group_ids
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-06-04 14:13:01 -04:00
|
|
|
def after_post_create
|
2013-07-07 22:44:55 -04:00
|
|
|
if !@topic.private_message? && @post.post_number > 1 && @post.post_type != Post.types[:moderator_action]
|
2013-06-04 14:13:01 -04:00
|
|
|
TopicTrackingState.publish_unread(@post)
|
2013-05-16 01:03:03 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-06-04 14:13:01 -04:00
|
|
|
def after_topic_create
|
2013-05-16 01:03:03 -04:00
|
|
|
|
|
|
|
# Don't publish invisible topics
|
2013-06-04 14:13:01 -04:00
|
|
|
return unless @topic.visible?
|
2013-05-16 01:03:03 -04:00
|
|
|
|
2013-07-07 22:44:55 -04:00
|
|
|
return if @topic.private_message? || @post.post_type == Post.types[:moderator_action]
|
2013-05-16 01:03:03 -04:00
|
|
|
|
2013-06-04 14:13:01 -04:00
|
|
|
@topic.posters = @topic.posters_summary
|
|
|
|
@topic.posts_count = 1
|
2013-05-16 01:03:03 -04:00
|
|
|
|
2013-06-04 14:13:01 -04:00
|
|
|
TopicTrackingState.publish_new(@topic)
|
2013-05-16 01:03:03 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
|
2013-05-12 21:48:01 -04:00
|
|
|
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
|
|
|
|
|
2013-06-04 14:13:01 -04:00
|
|
|
private
|
|
|
|
|
|
|
|
def setup_topic
|
|
|
|
if @opts[:topic_id].blank?
|
|
|
|
topic_creator = TopicCreator.new(@user, guardian, @opts)
|
2013-06-07 12:36:37 -04:00
|
|
|
|
|
|
|
begin
|
|
|
|
topic = topic_creator.create
|
|
|
|
@errors = topic_creator.errors
|
|
|
|
rescue ActiveRecord::Rollback => ex
|
|
|
|
# In the event of a rollback, grab the errors from the topic
|
|
|
|
@errors = topic_creator.errors
|
|
|
|
raise ex
|
|
|
|
end
|
2013-06-04 14:13:01 -04:00
|
|
|
|
|
|
|
@new_topic = true
|
|
|
|
else
|
|
|
|
topic = Topic.where(id: @opts[:topic_id]).first
|
|
|
|
guardian.ensure_can_create!(Post, topic)
|
2013-05-02 01:15:17 -04:00
|
|
|
end
|
2013-06-04 14:13:01 -04:00
|
|
|
@topic = topic
|
2013-05-02 01:15:17 -04:00
|
|
|
end
|
|
|
|
|
2013-07-22 01:06:53 -04:00
|
|
|
def update_topic_stats
|
|
|
|
# Update attributes on the topic - featured users and last posted.
|
|
|
|
attrs = {last_posted_at: @post.created_at, last_post_user_id: @post.user_id}
|
|
|
|
attrs[:bumped_at] = @post.created_at unless @post.no_bump
|
|
|
|
@topic.update_attributes(attrs)
|
|
|
|
end
|
|
|
|
|
2013-06-04 14:13:01 -04:00
|
|
|
def setup_post
|
|
|
|
post = @topic.posts.new(raw: @opts[:raw],
|
2013-06-29 17:57:10 -04:00
|
|
|
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
|
|
|
|
[:post_type, :no_bump, :cooking_options, :image_sizes, :acting_user, :invalidate_oneboxes].each do |a|
|
|
|
|
post.send("#{a}=", @opts[a]) if @opts[a].present?
|
|
|
|
end
|
|
|
|
|
2013-06-04 14:13:01 -04:00
|
|
|
post.extract_quoted_post_numbers
|
|
|
|
post.created_at = Time.zone.parse(@opts[:created_at].to_s) if @opts[:created_at].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
|
2013-05-27 19:13:53 -04:00
|
|
|
end
|
|
|
|
end
|
2013-05-02 01:15:17 -04:00
|
|
|
|
2013-06-04 14:13:01 -04:00
|
|
|
def save_post
|
2013-07-01 22:22:56 -04:00
|
|
|
unless @post.save(validate: !@opts[:skip_validations])
|
2013-06-04 14:13:01 -04:00
|
|
|
@errors = @post.errors
|
2013-05-27 19:13:53 -04:00
|
|
|
raise ActiveRecord::Rollback.new
|
2013-05-02 01:15:17 -04:00
|
|
|
end
|
|
|
|
end
|
2013-06-04 14:13:01 -04:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2013-08-07 13:02:49 -04:00
|
|
|
def consider_clearing_flags
|
|
|
|
if @topic.private_message? && @post.post_number > 1 && @topic.user_id != @post.user_id
|
|
|
|
clear_possible_flags(@topic)
|
2013-06-04 14:13:01 -04:00
|
|
|
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
|
2013-07-21 21:40:39 -04:00
|
|
|
unless @opts[:auto_track] == false
|
|
|
|
TopicUser.auto_track(@user.id, @topic.id, TopicUser.notification_reasons[:created_post])
|
2013-07-22 01:06:53 -04:00
|
|
|
# Update topic user data
|
|
|
|
TopicUser.change(@post.user.id,
|
|
|
|
@post.topic.id,
|
|
|
|
posted: true,
|
|
|
|
last_read_post_number: @post.post_number,
|
|
|
|
seen_post_count: @post.post_number)
|
2013-07-21 21:40:39 -04:00
|
|
|
end
|
2013-06-04 14:13:01 -04:00
|
|
|
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
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|