setting.rb 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. # Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/
  2. class Setting < ApplicationModel
  3. store :options
  4. store :state_current
  5. store :state_initial
  6. store :preferences
  7. before_create :state_check, :set_initial
  8. after_create :reset_change_id, :reset_cache, :check_broadcast
  9. before_update :state_check
  10. after_update :reset_change_id, :reset_cache, :check_broadcast
  11. after_commit :check_refresh
  12. attr_accessor :state
  13. @@current = {} # rubocop:disable Style/ClassVars
  14. @@raw = {} # rubocop:disable Style/ClassVars
  15. @@change_id = nil # rubocop:disable Style/ClassVars
  16. @@last_changed_at = nil # rubocop:disable Style/ClassVars
  17. @@lookup_at = nil # rubocop:disable Style/ClassVars
  18. @@lookup_timeout = if ENV['ZAMMAD_SETTING_TTL'] # rubocop:disable Style/ClassVars
  19. ENV['ZAMMAD_SETTING_TTL'].to_i.seconds
  20. else
  21. 15.seconds
  22. end
  23. =begin
  24. set config setting
  25. Setting.set('some_config_name', some_value)
  26. =end
  27. def self.set(name, value)
  28. setting = Setting.find_by(name: name)
  29. if !setting
  30. raise "Can't find config setting '#{name}'"
  31. end
  32. setting.state_current = { value: value }
  33. setting.save!
  34. logger.info "Setting.set('#{name}', #{value.inspect})"
  35. true
  36. end
  37. =begin
  38. get config setting
  39. value = Setting.get('some_config_name')
  40. =end
  41. def self.get(name)
  42. load
  43. @@current[name].deep_dup # prevents accidental modification of settings in console
  44. end
  45. =begin
  46. reset config setting to default
  47. Setting.reset('some_config_name')
  48. Setting.reset('some_config_name', force) # true|false - force it false per default
  49. =end
  50. def self.reset(name, force = false)
  51. setting = Setting.find_by(name: name)
  52. if !setting
  53. raise "Can't find config setting '#{name}'"
  54. end
  55. return true if !force && setting.state_current == setting.state_initial
  56. setting.state_current = setting.state_initial
  57. setting.save!
  58. logger.info "Setting.reset('#{name}', #{setting.state_current.inspect})"
  59. true
  60. end
  61. =begin
  62. reload config settings
  63. Setting.reload
  64. =end
  65. def self.reload
  66. @@last_changed_at = nil # rubocop:disable Style/ClassVars
  67. load(true)
  68. end
  69. private
  70. # load values and cache them
  71. def self.load(force = false)
  72. # check if config is already generated
  73. return false if !force && @@current.present? && cache_valid?
  74. # read all or only changed since last read
  75. latest = Setting.order(updated_at: :desc).limit(1).pluck(:updated_at)
  76. settings = if @@last_changed_at && @@current.present?
  77. Setting.where('updated_at >= ?', @@last_changed_at).order(:id).pluck(:name, :state_current)
  78. else
  79. Setting.order(:id).pluck(:name, :state_current)
  80. end
  81. Setting::Processed.process_settings! settings
  82. if latest && latest[0]
  83. @@last_changed_at = [Time.current, latest[0]].min # rubocop:disable Style/ClassVars
  84. end
  85. if settings.present?
  86. settings.each do |setting|
  87. @@raw[setting[0]] = setting[1]['value']
  88. end
  89. @@raw.each do |key, value|
  90. if value.class != String
  91. @@current[key] = value
  92. next
  93. end
  94. @@current[key] = value.gsub(%r{\#\{config\.(.+?)\}}) do
  95. @@raw[$1].to_s
  96. end
  97. end
  98. end
  99. @@change_id = Rails.cache.read('Setting::ChangeId') # rubocop:disable Style/ClassVars
  100. @@lookup_at = Time.now.to_i # rubocop:disable Style/ClassVars
  101. true
  102. end
  103. private_class_method :load
  104. # set initial value in state_initial
  105. def set_initial
  106. self.state_initial = state_current
  107. true
  108. end
  109. def reset_change_id
  110. @@current[name] = state_current[:value]
  111. change_id = SecureRandom.uuid
  112. logger.debug { "Setting.reset_change_id: set new cache, #{change_id}" }
  113. Rails.cache.write('Setting::ChangeId', change_id, { expires_in: 24.hours })
  114. @@lookup_at = nil # rubocop:disable Style/ClassVars
  115. true
  116. end
  117. def reset_cache
  118. return true if preferences[:cache].blank?
  119. preferences[:cache].each do |key|
  120. Rails.cache.delete(key)
  121. end
  122. true
  123. end
  124. # check if cache is still valid
  125. def self.cache_valid?
  126. if @@lookup_at && @@lookup_at > Time.now.to_i - @@lookup_timeout
  127. # logger.debug "Setting.cache_valid?: cache_id has been set within last #{@@lookup_timeout} seconds"
  128. return true
  129. end
  130. change_id = Rails.cache.read('Setting::ChangeId')
  131. if @@change_id && change_id == @@change_id
  132. @@lookup_at = Time.now.to_i # rubocop:disable Style/ClassVars
  133. # logger.debug "Setting.cache_valid?: cache still valid, #{@@change_id}/#{change_id}"
  134. return true
  135. end
  136. # logger.debug "Setting.cache_valid?: cache has changed, #{@@change_id}/#{change_id}"
  137. false
  138. end
  139. private_class_method :cache_valid?
  140. # convert state into hash to be able to store it as store
  141. def state_check
  142. return true if state.nil? # allow false value
  143. return true if state.try(:key?, :value)
  144. self.state_current = { value: state }
  145. true
  146. end
  147. # Notify clients about config changes.
  148. def check_broadcast
  149. return true if frontend != true
  150. value = state_current
  151. if state_current.key?(:value)
  152. value = state_current[:value]
  153. end
  154. Sessions.broadcast(
  155. {
  156. event: 'config_update',
  157. data: { name: name, value: value }
  158. },
  159. preferences[:authentication] ? 'authenticated' : 'public'
  160. )
  161. Gql::Subscriptions::ConfigUpdates.trigger(self)
  162. true
  163. end
  164. # NB: Force users to reload on SAML credentials config changes
  165. # This is needed because the setting is not frontend related,
  166. # so we can't rely on 'config_update_local' mechanism to kick in
  167. # https://github.com/zammad/zammad/issues/4263
  168. def check_refresh
  169. return if ['auth_saml_credentials'].exclude?(name)
  170. AppVersion.set(true, AppVersion::MSG_CONFIG_CHANGED)
  171. end
  172. end