mirror of
https://github.com/discourse/discourse.git
synced 2024-11-23 01:16:38 -06:00
227873df78
once enable_mailing_list_mode is enabled any user can elect to get every post via email unless they opt out of category or topic
309 lines
9.2 KiB
Ruby
309 lines
9.2 KiB
Ruby
# Responsible for creating posts and topics
|
|
#
|
|
require_dependency 'rate_limiter'
|
|
require_dependency 'topic_creator'
|
|
|
|
class PostCreator
|
|
|
|
attr_reader :errors, :opts
|
|
|
|
# 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)
|
|
# auto_track - Automatically track this topic if needed (default true)
|
|
#
|
|
# 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
|
|
# cooking_options - Options for rendering the text
|
|
#
|
|
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
|
|
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
|
|
consider_clearing_flags
|
|
track_topic
|
|
update_topic_stats
|
|
update_user_counts
|
|
publish
|
|
ensure_in_allowed_users if guardian.is_staff?
|
|
@post.advance_draft_sequence
|
|
@post.save_reply_relationships
|
|
end
|
|
|
|
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(', ')} } )
|
|
elsif @post && !@post.errors.present? && !@opts[:skip_validations]
|
|
SpamRulesEnforcer.enforce!(@post)
|
|
end
|
|
|
|
track_latest_on_category
|
|
|
|
enqueue_jobs
|
|
@post
|
|
end
|
|
|
|
|
|
def self.create(user, opts)
|
|
PostCreator.new(user, opts).create
|
|
end
|
|
|
|
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.word_count = post.raw.scan(/\w+/).size
|
|
post.post_number ||= Topic.next_post_number(post.topic_id, post.reply_to_post_number.present?)
|
|
|
|
cooking_options = post.cooking_options || {}
|
|
cooking_options[:topic_id] = post.topic_id
|
|
|
|
post.cooked ||= post.cook(post.raw, cooking_options)
|
|
post.sort_order = post.post_number
|
|
DiscourseEvent.trigger(:before_create_post, post)
|
|
post.last_version_at ||= Time.now
|
|
end
|
|
|
|
|
|
protected
|
|
|
|
def track_latest_on_category
|
|
if @post && @post.errors.count == 0 && @topic && @topic.category_id
|
|
Category.where(id: @topic.category_id).update_all(latest_post_id: @post.id)
|
|
if @post.post_number == 1
|
|
Category.where(id: @topic.category_id).update_all(latest_topic_id: @topic.id)
|
|
end
|
|
end
|
|
end
|
|
|
|
def ensure_in_allowed_users
|
|
return unless @topic.private_message?
|
|
|
|
unless @topic.topic_allowed_users.where(user_id: @user.id).exists?
|
|
@topic.topic_allowed_users.create!(user_id: @user.id)
|
|
end
|
|
end
|
|
|
|
def secure_group_ids(topic)
|
|
@secure_group_ids ||= if topic.category && topic.category.read_restricted?
|
|
topic.category.secure_group_ids
|
|
end
|
|
end
|
|
|
|
def after_post_create
|
|
if !@topic.private_message? && @post.post_type != Post.types[:moderator_action]
|
|
if @post.post_number > 1
|
|
TopicTrackingState.publish_unread(@post)
|
|
end
|
|
if SiteSetting.enable_mailing_list_mode
|
|
Jobs.enqueue_in(
|
|
SiteSetting.email_time_window_mins.minutes,
|
|
:notify_mailing_list_subscribers,
|
|
post_id: @post.id
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
def after_topic_create
|
|
# Don't publish invisible topics
|
|
return unless @topic.visible?
|
|
|
|
return if @topic.private_message? || @post.post_type == Post.types[:moderator_action]
|
|
|
|
@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)
|
|
|
|
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
|
|
|
|
@new_topic = true
|
|
else
|
|
topic = Topic.where(id: @opts[:topic_id]).first
|
|
guardian.ensure_can_create!(Post, topic)
|
|
end
|
|
@topic = topic
|
|
end
|
|
|
|
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
|
|
attrs[:word_count] = (@topic.word_count || 0) + @post.word_count
|
|
@topic.update_attributes(attrs)
|
|
end
|
|
|
|
def setup_post
|
|
post = @topic.posts.new(raw: @opts[:raw],
|
|
user: @user,
|
|
reply_to_post_number: @opts[:reply_to_post_number])
|
|
|
|
# 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
|
|
|
|
post.cook_method = @opts[:cook_method] if @opts[:cook_method].present?
|
|
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
|
|
end
|
|
end
|
|
|
|
def save_post
|
|
unless @post.save(validate: !@opts[:skip_validations])
|
|
@errors = @post.errors
|
|
raise ActiveRecord::Rollback.new
|
|
end
|
|
end
|
|
|
|
def store_unique_post_key
|
|
@post.store_unique_post_key
|
|
end
|
|
|
|
def consider_clearing_flags
|
|
if @topic.private_message? && @post.post_number > 1 && @topic.user_id != @post.user_id
|
|
clear_possible_flags(@topic)
|
|
end
|
|
end
|
|
|
|
def update_user_counts
|
|
# We don't count replies to your own topics
|
|
if @user.id != @topic.user_id
|
|
@user.user_stat.update_topic_reply_count
|
|
@user.user_stat.save!
|
|
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
|
|
unless @opts[:auto_track] == false
|
|
TopicUser.auto_track(@user.id, @topic.id, TopicUser.notification_reasons[:created_post])
|
|
# 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)
|
|
end
|
|
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
|