TopicStatusUpdater = Struct.new(:topic, :user) do def update!(status, enabled, opts={}) status = Status.new(status, enabled) @topic_status_update = topic.topic_status_update Topic.transaction do change(status, opts) highest_post_number = topic.highest_post_number create_moderator_post_for(status, opts[:message]) update_read_state_for(status, highest_post_number) end end private def change(status, opts={}) if status.pinned? || status.pinned_globally? topic.update_pinned(status.enabled?, status.pinned_globally?, opts[:until]) elsif status.autoclosed? topic.update_column('closed', status.enabled?) else topic.update_column(status.name, status.enabled?) end if @topic_status_update if status.manually_closing_topic? || status.closing_topic? topic.set_or_create_status_update(TopicStatusUpdate.types[:close], nil) elsif status.manually_opening_topic? || status.opening_topic? topic.set_or_create_status_update(TopicStatusUpdate.types[:open], nil) end end # remove featured topics if we close/archive/make them invisible. Previously we used # to run the whole featuring logic but that could be very slow and have concurrency # errors on large sites with many autocloses and topics being created. if ((status.enabled? && (status.autoclosed? || status.closed? || status.archived?)) || (status.disabled? && status.visible?)) CategoryFeaturedTopic.where(topic_id: topic.id).delete_all end end def create_moderator_post_for(status, message=nil) topic.add_moderator_post(user, message || message_for(status), options_for(status)) topic.reload end def update_read_state_for(status, old_highest_read) if status.autoclosed? && status.enabled? # let's pretend all the people that read up to the autoclose message # actually read the topic PostTiming.pretend_read(topic.id, old_highest_read, topic.highest_post_number) end end def message_for(status) if status.autoclosed? locale_key = status.locale_key locale_key << "_lastpost" if @topic_status_update&.based_on_last_post message_for_autoclosed(locale_key) end end def message_for_autoclosed(locale_key) num_minutes = if @topic_status_update&.based_on_last_post @topic_status_update.duration.hours elsif @topic_status_update&.created_at Time.zone.now - @topic_status_update.created_at else Time.zone.now - topic.created_at end num_minutes = (num_minutes / 1.minute).round if num_minutes.minutes >= 2.days I18n.t("#{locale_key}_days", count: (num_minutes.minutes / 1.day).round) else num_hours = (num_minutes.minutes / 1.hour).round if num_hours >= 2 I18n.t("#{locale_key}_hours", count: num_hours) else I18n.t("#{locale_key}_minutes", count: num_minutes) end end end def options_for(status) { bump: status.opening_topic?, post_type: Post.types[:small_action], action_code: status.action_code } end Status = Struct.new(:name, :enabled) do %w(pinned_globally pinned autoclosed closed visible archived).each do |status| define_method("#{status}?") { name == status } end def enabled? enabled end def disabled? !enabled? end def action_code "#{name}.#{enabled? ? 'enabled' : 'disabled'}" end def locale_key "topic_statuses.#{action_code.tr('.', '_')}" end def opening_topic? (closed? || autoclosed?) && disabled? end def closing_topic? (closed? || autoclosed?) && enabled? end def manually_closing_topic? closed? && enabled? end def manually_opening_topic? closed? && disabled? end end end