# frozen_string_literal: true TopicStatusUpdater = Struct.new(:topic, :user) do def update!(status, enabled, opts = {}) status = Status.new(status, enabled) @topic_timer = topic.public_topic_timer updated = nil Topic.transaction do updated = change(status, opts) if updated highest_post_number = topic.highest_post_number create_moderator_post_for(status, opts) update_read_state_for( status, highest_post_number, silent_tracking: opts[:silent_tracking], ) end end updated end private def change(status, opts = {}) result = true if status.pinned? || status.pinned_globally? topic.update_pinned(status.enabled?, status.pinned_globally?, opts[:until]) elsif status.autoclosed? rc = Topic.where(id: topic.id, closed: !status.enabled?).update_all(closed: status.enabled?) topic.closed = status.enabled? result = false if rc == 0 else rc = Topic.where(:id => topic.id, status.name => !status.enabled).update_all( status.name => status.enabled?, ) topic.public_send("#{status.name}=", status.enabled?) result = false if rc == 0 end DiscourseEvent.trigger(:topic_closed, topic) if status.manually_closing_topic? if status.visible? && status.disabled? UserProfile.remove_featured_topic_from_all_profiles(topic) end if status.visible? && result topic.update_category_topic_count_by(status.enabled? ? 1 : -1) UserStatCountUpdater.public_send( status.enabled? ? :increment! : :decrement!, topic.first_post, ) end if status.visible? topic.update( visibility_reason_id: opts[:visibility_reason_id] || Topic.visibility_reasons[:unknown], ) end if @topic_timer if status.manually_closing_topic? || status.closing_topic? topic.delete_topic_timer(TopicTimer.types[:close]) topic.delete_topic_timer(TopicTimer.types[:silent_close]) elsif status.manually_opening_topic? || status.opening_topic? topic.delete_topic_timer(TopicTimer.types[:open]) topic.inherit_auto_close_from_category 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 result end def create_moderator_post_for(status, opts) message = opts[:message] topic.add_moderator_post(user, message || message_for(status), options_for(status, opts)) topic.reload end def update_read_state_for(status, old_highest_read, silent_tracking: false) if (status.autoclosed? && status.enabled?) || (status.closed? && silent_tracking) # 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 if status.closed? && status.enabled? sql_query = <<-SQL SELECT DISTINCT post_timings.user_id FROM post_timings JOIN user_options ON user_options.user_id = post_timings.user_id WHERE post_timings.topic_id = :topic_id AND user_options.topics_unread_when_closed = 'f' SQL user_ids = DB.query_single(sql_query, topic_id: topic.id) if user_ids.present? PostTiming.pretend_read(topic.id, old_highest_read, topic.highest_post_number, user_ids) end end end def message_for(status) if status.autoclosed? locale_key = status.locale_key.dup locale_key << "_lastpost" if @topic_timer&.based_on_last_post message_for_autoclosed(locale_key) end end def message_for_autoclosed(locale_key) num_minutes = if @topic_timer&.based_on_last_post (@topic_timer.duration_minutes || 0).minutes.to_i elsif @topic_timer&.created_at Time.zone.now - @topic_timer.created_at else Time.zone.now - topic.created_at end # all of the results above are in seconds, this brings them # back to the actual minutes integer 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, opts = {}) { bump: status.opening_topic?, post_type: Post.types[:small_action], silent: opts[:silent], 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