require_dependency 'has_errors' class TopicCreator attr_reader :user, :guardian, :opts include HasErrors def self.create(user, guardian, opts) self.new(user, guardian, opts).create end def initialize(user, guardian, opts) @user = user @guardian = guardian @opts = opts @added_users = [] end def valid? topic = Topic.new(setup_topic_params) # validate? will clear the error hash # so we fire the validation event after # this allows us to add errors valid = topic.valid? # not sure where this should go if !@guardian.is_staff? && staff_only = DiscourseTagging.staff_only_tags(@opts[:tags]) topic.errors[:base] << I18n.t("tags.staff_tag_disallowed", tag: staff_only.join(" ")) end DiscourseEvent.trigger(:after_validate_topic, topic, self) valid &&= topic.errors.empty? add_errors_from(topic) unless valid valid end def create topic = Topic.new(setup_topic_params) setup_tags(topic) DiscourseEvent.trigger(:before_create_topic, topic, self) setup_auto_close_time(topic) process_private_message(topic) save_topic(topic) create_warning(topic) watch_topic(topic) topic end private def create_warning(topic) return unless @opts[:is_warning] # We can only attach warnings to PMs rollback_with!(topic, :warning_requires_pm) unless topic.private_message? # Don't create it if there is more than one user rollback_with!(topic, :too_many_users) if @added_users.size != 1 # Create a warning record UserWarning.create(topic: topic, user: @added_users.first, created_by: @user) end def watch_topic(topic) unless @opts[:auto_track] == false topic.notifier.watch_topic!(topic.user_id) end topic.topic_allowed_users(true).each do |tau| next if tau.user_id == -1 || tau.user_id == topic.user_id topic.notifier.watch!(tau.user_id) end topic.topic_allowed_groups(true).each do |tag| tag.group.group_users.each do |gu| next if gu.user_id == -1 || gu.user_id == topic.user_id action = case gu.notification_level when TopicUser.notification_levels[:tracking] then "track!" when TopicUser.notification_levels[:regular] then "regular!" when TopicUser.notification_levels[:muted] then "mute!" when TopicUser.notification_levels[:watching] then "watch!" else "track!" end topic.notifier.send(action, gu.user_id) end end end def setup_topic_params @opts[:visible] = true if @opts[:visible].nil? topic_params = { title: @opts[:title], user_id: @user.id, last_post_user_id: @user.id, visible: @opts[:visible] } [:subtype, :archetype, :meta_data, :import_mode].each do |key| topic_params[key] = @opts[key] if @opts[key].present? end if topic_params[:import_mode] && @opts[:views].to_i > 0 topic_params[:views] = @opts[:views].to_i end # Automatically give it a moderator warning subtype if specified topic_params[:subtype] = TopicSubtype.moderator_warning if @opts[:is_warning] category = find_category @guardian.ensure_can_create!(Topic, category) unless (@opts[:skip_validations] || @opts[:archetype] == Archetype.private_message) topic_params[:category_id] = category.id if category.present? topic_params[:created_at] = Time.zone.parse(@opts[:created_at].to_s) if @opts[:created_at].present? topic_params[:pinned_at] = Time.zone.parse(@opts[:pinned_at].to_s) if @opts[:pinned_at].present? topic_params[:pinned_globally] = @opts[:pinned_globally] if @opts[:pinned_globally].present? if SiteSetting.topic_featured_link_enabled && @opts[:featured_link].present? && @guardian.can_edit_featured_link?(topic_params[:category_id]) topic_params[:featured_link] = @opts[:featured_link] end topic_params end def find_category # PM can't have a category @opts.delete(:category) if @opts[:archetype].present? && @opts[:archetype] == Archetype.private_message # Temporary fix to allow older clients to create topics. # When all clients are updated the category variable should # be set directly to the contents of the if statement. if (@opts[:category].is_a? Integer) || (@opts[:category] =~ /^\d+$/) Category.find_by(id: @opts[:category]) else Category.find_by(name_lower: @opts[:category].try(:downcase)) end end def setup_tags(topic) DiscourseTagging.tag_topic_by_names(topic, @guardian, @opts[:tags]) end def setup_auto_close_time(topic) return unless @opts[:auto_close_time].present? return unless @guardian.can_moderate?(topic) topic.set_auto_close(@opts[:auto_close_time], by_user: @user) end def process_private_message(topic) return unless @opts[:archetype] == Archetype.private_message topic.subtype = TopicSubtype.user_to_user unless topic.subtype unless @opts[:target_usernames].present? || @opts[:target_emails].present? || @opts[:target_group_names].present? rollback_with!(topic, :no_user_selected) end if @opts[:target_emails].present? && !@guardian.cand_send_private_messages_to_email? then rollback_with!(topic, :reply_by_email_disabled) end add_users(topic, @opts[:target_usernames]) add_emails(topic, @opts[:target_emails]) add_groups(topic, @opts[:target_group_names]) topic.topic_allowed_users.build(user_id: @user.id) end def save_topic(topic) topic.disable_rate_limits! if @opts[:skip_validations] unless topic.save(validate: !@opts[:skip_validations]) rollback_from_errors!(topic) end end def add_users(topic, usernames) return unless usernames names = usernames.split(',').flatten len = 0 User.where(username: names).each do |user| check_can_send_permission!(topic, user) @added_users << user topic.topic_allowed_users.build(user_id: user.id) len += 1 end rollback_with!(topic, :target_user_not_found) unless len == names.length end def add_emails(topic, emails) return unless emails emails = emails.split(',').flatten len = 0 emails.each do |email| display_name = email.split("@").first user = find_or_create_user(email, display_name) @added_users << user topic.topic_allowed_users.build(user_id: user.id) len += 1 end rollback_with!(topic, :target_user_not_found) unless len == emails.length end def add_groups(topic, groups) return unless groups names = groups.split(',').flatten len = 0 Group.where(name: names).each do |group| check_can_send_permission!(topic, group) topic.topic_allowed_groups.build(group_id: group.id) len += 1 group.update_columns(has_messages: true) unless group.has_messages end rollback_with!(topic, :target_group_not_found) unless len == names.length end def check_can_send_permission!(topic, obj) rollback_with!(topic, :cant_send_pm) unless @opts[:skip_validations] || @guardian.can_send_private_message?(obj) end def find_or_create_user(email, display_name) user = User.find_by_email(email) if user.nil? && SiteSetting.enable_staged_users username = UserNameSuggester.sanitize_username(display_name) if display_name.present? user = User.create!( email: email, username: UserNameSuggester.suggest(username.presence || email), name: display_name.presence || User.suggest_name(email), staged: true ) end user rescue rollback_with!(topic, :target_user_not_found) end end