# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ module CanBePublished extend ActiveSupport::Concern def can_be_published_aasm @can_be_published_aasm ||= StateMachine.new(self) end def visible? can_be_published_aasm.published? end def visible_internally? can_be_published_aasm.internal? || visible? end class_methods do def inverse_relation_name(scope_name) "can_be_published_#{scope_name}_#{model_name.plural}" end end included do validate :archived_after_internal validate :archived_after_published validate :published_after_internal before_save :update_user_references after_save :schedule_touch after_save :update_active_publicly after_destroy :update_active_publicly after_touch :update_active_publicly %i[archived published internal].each do |scope_name| local = :"#{scope_name}_by" remote = inverse_relation_name(scope_name).to_sym belongs_to local, class_name: 'User', inverse_of: remote, optional: true # Deletion of users is handled in User.destroy_move_dependency_ownership and resets fields to user_id: 1, so skip dependent: here. User.has_many remote, class_name: model_name, inverse_of: local, foreign_key: "#{local}_id" # rubocop:disable Rails/HasManyOrHasOneDependent User.association_attributes_ignored remote end scope :published, lambda { timestamp = Time.zone.now date_earlier(:published_at, timestamp).date_later_or_nil(:archived_at, timestamp) } scope :archived, lambda { timestamp = Time.zone.now date_earlier(:archived_at, timestamp) } scope :only_internal, lambda { timestamp = Time.zone.now date_earlier(:internal_at, timestamp) .date_later_or_nil(:archived_at, timestamp) .date_later_or_nil(:published_at, timestamp) } scope :internal, lambda { timestamp = Time.zone.now internal = arel_table[:internal_at].lt(timestamp) published = arel_table[:published_at].lt(timestamp) where(internal.or(published)) .date_later_or_nil(:archived_at, timestamp) } scope :date_earlier, lambda { |field, timestamp| where arel_table[field].lt(timestamp) } scope :date_later_or_nil, lambda { |field, timestamp| where arel_table[field].gt(timestamp).or(arel_table[field].eq(nil)) } end def update_user_references return if can_be_published_aasm.aasm.current_event.present? # state machine is handling it %i[archived internal published].each do |scope_name| update_user_reference_item(scope_name) end end def update_user_reference_item(scope_name) return if !send(:"#{scope_name}_at_changed?") send(:"#{scope_name}_by_id=", UserInfo.current_user_id) end def archived_after_internal return if internal_at.nil? || archived_at.nil? || archived_at >= internal_at errors.add(:archived_at, __('date must be no earlier than internal date')) end def archived_after_published return if published_at.nil? || archived_at.nil? || archived_at >= published_at errors.add(:archived_at, __('date must be no earlier than published date')) end def published_after_internal return if published_at.nil? || internal_at.nil? || published_at >= internal_at errors.add(:published_at, __('date must be no earlier than internal date')) end def schedule_touch_for(attr) date = saved_changes[attr]&.last return if date.nil? || date <= Time.zone.now ScheduledTouchJob.touch_at(self, date) end def schedule_touch %i[published_at archived_at].each { |attr| schedule_touch_for(attr) } end def update_active_publicly CanBePublished.update_active_publicly! end def self.update_active_publicly! Setting.set('kb_active_publicly', active_publicly?) end def self.active_publicly? KnowledgeBase::Answer .published .joins(category: :knowledge_base) .exists?(knowledge_bases: { active: true }) end end