Module: SiteSettingExtension

Includes:
HasSanitizableFields, SiteSettings::DeprecatedSettings
Included in:
SiteSetting, SiteSettingMoveToGroupsMigrationGenerator
Defined in:
lib/site_setting_extension.rb

Constant Summary collapse

HOSTNAME_SETTINGS =
%w[
  disabled_image_download_domains
  blocked_onebox_domains
  exclude_rel_nofollow_domains
  blocked_email_domains
  allowed_email_domains
  allowed_spam_host_domains
]

Constants included from SiteSettings::DeprecatedSettings

SiteSettings::DeprecatedSettings::OVERRIDE_TL_GROUP_SETTINGS, SiteSettings::DeprecatedSettings::SETTINGS

Class Method Summary collapse

Instance Method Summary collapse

Methods included from HasSanitizableFields

#sanitize_field

Methods included from SiteSettings::DeprecatedSettings

#group_to_tl, #is_tl_and_staff_setting?, #setup_deprecated_methods, #tl_to_group

Class Method Details

.client_settings_cache_keyObject



344
345
346
347
348
349
# File 'lib/site_setting_extension.rb', line 344

def self.client_settings_cache_key
  # NOTE: we use the git version in the key to ensure
  # that we don't end up caching the incorrect version
  # in cases where we are cycling unicorns
  "client_settings_json_#{Discourse.git_version}"
end

.extended(klass) ⇒ Object

support default_locale being set via global settings this also adds support for testing the extension and global settings for site locale



10
11
12
13
14
15
# File 'lib/site_setting_extension.rb', line 10

def self.extended(klass)
  if GlobalSetting.respond_to?(:default_locale) && GlobalSetting.default_locale.present?
    # protected
    klass.send :setup_shadowed_methods, :default_locale, GlobalSetting.default_locale
  end
end

Instance Method Details

#add_override!(name, val) ⇒ Object



431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
# File 'lib/site_setting_extension.rb', line 431

def add_override!(name, val)
  old_val = current[name]
  val, type = type_supervisor.to_db_value(name, val)

  sanitize_override = val.is_a?(String) && client_settings.include?(name)

  sanitized_val = sanitize_override ? sanitize_field(val) : val

  if mandatory_values[name.to_sym]
    sanitized_val =
      (mandatory_values[name.to_sym].split("|") | sanitized_val.to_s.split("|")).join("|")
  end

  provider.save(name, sanitized_val, type)
  current[name] = type_supervisor.to_rb_value(name, sanitized_val)

  return if current[name] == old_val

  clear_uploads_cache(name)
  notify_clients!(name) if client_settings.include? name
  clear_cache!
  if old_val != current[name]
    DiscourseEvent.trigger(:site_setting_changed, name, old_val, current[name])
  end
end

#after_forkObject



412
413
414
415
# File 'lib/site_setting_extension.rb', line 412

def after_fork
  @process_id = nil
  ensure_listen_for_changes
end

#all_settings(include_hidden: false, include_locale_setting: true, only_overridden: false, filter_categories: nil, filter_plugin: nil, filter_names: nil, filter_allowed_hidden: nil, filter_area: nil) ⇒ Object

Retrieve all settings



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/site_setting_extension.rb', line 200

def all_settings(
  include_hidden: false,
  include_locale_setting: true,
  only_overridden: false,
  filter_categories: nil,
  filter_plugin: nil,
  filter_names: nil,
  filter_allowed_hidden: nil,
  filter_area: nil
)
  locale_setting_hash = {
    setting: "default_locale",
    default: SiteSettings::DefaultsProvider::DEFAULT_LOCALE,
    category: "required",
    description: description("default_locale"),
    type: SiteSetting.types[SiteSetting.types[:enum]],
    preview: nil,
    value: self.default_locale,
    valid_values: LocaleSiteSetting.values,
    translate_names: LocaleSiteSetting.translate_names?,
  }

  include_locale_setting = false if filter_categories.present? || filter_plugin.present?

  defaults
    .all(default_locale)
    .reject do |setting_name, _|
      plugins[name] && !Discourse.plugins_by_name[plugins[name]].configurable?
    end
    .select do |setting_name, _|
      is_hidden = hidden_settings.include?(setting_name)

      next true if !is_hidden
      next false if !include_hidden
      next true if filter_allowed_hidden.nil?

      filter_allowed_hidden.include?(setting_name)
    end
    .select do |setting_name, _|
      if filter_categories && filter_categories.any?
        filter_categories.include?(categories[setting_name])
      else
        true
      end
    end
    .select do |setting_name, _|
      if filter_area
        Array.wrap(areas[setting_name]).include?(filter_area)
      else
        true
      end
    end
    .select do |setting_name, _|
      if filter_plugin
        plugins[setting_name] == filter_plugin
      else
        true
      end
    end
    .map do |s, v|
      type_hash = type_supervisor.type_hash(s)
      default = defaults.get(s, default_locale).to_s

      value = public_send(s)
      value = value.map(&:to_s).join("|") if type_hash[:type].to_s == "uploaded_image_list"

      if type_hash[:type].to_s == "upload" && default.to_i < Upload::SEEDED_ID_THRESHOLD
        default = default_uploads[default.to_i]
      end

      opts = {
        setting: s,
        description: description(s),
        keywords: keywords(s),
        default: default,
        value: value.to_s,
        category: categories[s],
        preview: previews[s],
        secret: secret_settings.include?(s),
        placeholder: placeholder(s),
        mandatory_values: mandatory_values[s],
        requires_confirmation: requires_confirmation_settings[s],
      }.merge!(type_hash)

      opts[:plugin] = plugins[s] if plugins[s]

      opts
    end
    .select do |setting|
      if only_overridden
        setting[:value] != setting[:default]
      else
        true
      end
    end
    .select do |setting|
      if filter_names
        filter_names.include?(setting[:setting].to_s)
      else
        true
      end
    end
    .unshift(include_locale_setting && !only_overridden ? locale_setting_hash : nil)
    .compact
end

#areasObject



90
91
92
# File 'lib/site_setting_extension.rb', line 90

def areas
  @areas ||= {}
end

#categoriesObject



86
87
88
# File 'lib/site_setting_extension.rb', line 86

def categories
  @categories ||= {}
end

#client_settingsObject



118
119
120
# File 'lib/site_setting_extension.rb', line 118

def client_settings
  @client_settings ||= [:default_locale]
end

#client_settings_jsonObject



166
167
168
169
170
171
172
173
174
# File 'lib/site_setting_extension.rb', line 166

def client_settings_json
  key = SiteSettingExtension.client_settings_cache_key
  json = Discourse.cache.fetch(key, expires_in: 30.minutes) { client_settings_json_uncached }
  Rails.logger.error("Nil client_settings_json from the cache for '#{key}'") if json.nil?
  json || ""
rescue => e
  Rails.logger.error("Error while retrieving client_settings_json: #{e.message}")
  ""
end

#client_settings_json_uncachedObject



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/site_setting_extension.rb', line 176

def client_settings_json_uncached
  MultiJson.dump(
    Hash[
      *@client_settings.flat_map do |name|
        value =
          if deprecated_settings.include?(name.to_s)
            public_send(name, warn: false)
          else
            public_send(name)
          end
        type = type_supervisor.get_type(name)
        value = value.to_s if type == :upload
        value = value.map(&:to_s).join("|") if type == :uploaded_image_list

        [name, value]
      end
    ],
  )
rescue => e
  Rails.logger.error("Error while generating client_settings_json_uncached: #{e.message}")
  nil
end

#currentObject



73
74
75
76
# File 'lib/site_setting_extension.rb', line 73

def current
  @containers ||= {}
  @containers[provider.current_site] ||= {}
end

#default_localeObject

set up some sort of default so we can look stuff up



33
34
35
36
37
38
39
40
41
42
# File 'lib/site_setting_extension.rb', line 33

def default_locale
  # note optimised cause this is called a lot so avoiding .presence which
  # adds 2 method calls
  locale = current[:default_locale]
  if locale && !locale.blank?
    locale
  else
    SiteSettings::DefaultsProvider::DEFAULT_LOCALE
  end
end

#default_locale=(val) ⇒ Object

we need a default here to support defaults per locale



18
19
20
21
22
23
24
25
26
# File 'lib/site_setting_extension.rb', line 18

def default_locale=(val)
  val = val.to_s
  raise Discourse::InvalidParameters.new(:value) unless LocaleSiteSetting.valid_value?(val)
  if val != self.default_locale
    add_override!(:default_locale, val)
    refresh!
    Discourse.request_refresh!
  end
end

#default_locale?Boolean

Returns:

  • (Boolean)


28
29
30
# File 'lib/site_setting_extension.rb', line 28

def default_locale?
  true
end

#defaultsObject



78
79
80
# File 'lib/site_setting_extension.rb', line 78

def defaults
  @defaults ||= SiteSettings::DefaultsProvider.new(self)
end

#deprecated_setting_alias(setting_name) ⇒ Object



146
147
148
149
150
# File 'lib/site_setting_extension.rb', line 146

def deprecated_setting_alias(setting_name)
  SiteSettings::DeprecatedSettings::SETTINGS
    .find { |setting| setting.second.to_s == setting_name.to_s }
    &.first
end

#deprecated_settingsObject



142
143
144
# File 'lib/site_setting_extension.rb', line 142

def deprecated_settings
  @deprecated_settings ||= SiteSettings::DeprecatedSettings::SETTINGS.map(&:first).to_set
end

#description(setting) ⇒ Object



306
307
308
# File 'lib/site_setting_extension.rb', line 306

def description(setting)
  I18n.t("site_settings.#{setting}", base_path: Discourse.base_path, default: "")
end

#ensure_listen_for_changesObject



387
388
389
390
391
392
393
394
395
396
397
# File 'lib/site_setting_extension.rb', line 387

def ensure_listen_for_changes
  return if @listen_for_changes == false

  unless @subscribed
    MessageBus.subscribe("/site_settings") do |message|
      process_message(message) if message.data["process"] != process_id
    end

    @subscribed = true
  end
end

#filter_value(name, value) ⇒ Object



478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
# File 'lib/site_setting_extension.rb', line 478

def filter_value(name, value)
  if HOSTNAME_SETTINGS.include?(name)
    value
      .split("|")
      .map do |url|
        url.strip!
        get_hostname(url)
      end
      .compact
      .uniq
      .join("|")
  else
    value
  end
end

#get(name) ⇒ Object



528
529
530
531
532
533
534
535
536
# File 'lib/site_setting_extension.rb', line 528

def get(name)
  if has_setting?(name)
    self.public_send(name)
  else
    raise Discourse::InvalidParameters.new(
            I18n.t("errors.site_settings.invalid_site_setting", name: name),
          )
  end
end

#has_setting?(v) ⇒ Boolean

Returns:

  • (Boolean)


44
45
46
# File 'lib/site_setting_extension.rb', line 44

def has_setting?(v)
  defaults.has_setting?(v)
end

#hidden_settingsObject



110
111
112
# File 'lib/site_setting_extension.rb', line 110

def hidden_settings
  hidden_settings_provider.all
end

#hidden_settings_providerObject



106
107
108
# File 'lib/site_setting_extension.rb', line 106

def hidden_settings_provider
  @hidden_settings_provider ||= SiteSettings::HiddenProvider.new
end

#info(name) ⇒ Object

Convenience method for debugging site setting issues Returns a hash with information about a specific setting



541
542
543
544
545
546
547
548
549
550
551
# File 'lib/site_setting_extension.rb', line 541

def info(name)
  {
    resolved_value: get(name),
    default_value: defaults[name],
    global_override: GlobalSetting.respond_to?(name) ? GlobalSetting.public_send(name) : nil,
    database_value: provider.find(name)&.value,
    refresh?: refresh_settings.include?(name),
    client?: client_settings.include?(name),
    secret?: secret_settings.include?(name),
  }
end

#keywords(setting) ⇒ Object



310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/site_setting_extension.rb', line 310

def keywords(setting)
  translated_keywords = I18n.t("site_settings.keywords.#{setting}", default: "")
  english_translated_keywords = []

  if I18n.locale != :en
    english_translated_keywords =
      I18n.t("site_settings.keywords.#{setting}", default: "", locale: :en).split("|")
  end

  # TODO (martin) We can remove this workaround of checking if
  # we get an array back once keyword translations in languages other
  # than English have been updated not to use YAML arrays.
  if translated_keywords.is_a?(Array)
    return(
      (
        translated_keywords + [deprecated_setting_alias(setting)] + english_translated_keywords
      ).compact
    )
  end

  translated_keywords
    .split("|")
    .concat([deprecated_setting_alias(setting)] + english_translated_keywords)
    .compact
end

#listen_for_changes=(val) ⇒ Object



56
57
58
# File 'lib/site_setting_extension.rb', line 56

def listen_for_changes=(val)
  @listen_for_changes = val
end

#load_settings(file, plugin: nil) ⇒ Object



134
135
136
137
138
139
140
# File 'lib/site_setting_extension.rb', line 134

def load_settings(file, plugin: nil)
  SiteSettings::YamlLoader
    .new(file)
    .load do |category, name, default, opts|
      setting(name, default, opts.merge(category: category, plugin: plugin))
    end
end

#mandatory_valuesObject



94
95
96
# File 'lib/site_setting_extension.rb', line 94

def mandatory_values
  @mandatory_values ||= {}
end

#mutexObject



69
70
71
# File 'lib/site_setting_extension.rb', line 69

def mutex
  @mutex ||= Mutex.new
end

#notify_changed!Object



457
458
459
# File 'lib/site_setting_extension.rb', line 457

def notify_changed!
  MessageBus.publish("/site_settings", process: process_id)
end

#notify_clients!(name) ⇒ Object



461
462
463
# File 'lib/site_setting_extension.rb', line 461

def notify_clients!(name)
  MessageBus.publish("/client_settings", name: name, value: self.public_send(name))
end

#placeholder(setting) ⇒ Object



336
337
338
339
340
341
342
# File 'lib/site_setting_extension.rb', line 336

def placeholder(setting)
  if !I18n.t("site_settings.placeholder.#{setting}", default: "").empty?
    I18n.t("site_settings.placeholder.#{setting}")
  elsif SiteIconManager.respond_to?("#{setting}_url")
    SiteIconManager.public_send("#{setting}_url")
  end
end

#pluginsObject



130
131
132
# File 'lib/site_setting_extension.rb', line 130

def plugins
  @plugins ||= {}
end

#previewsObject



122
123
124
# File 'lib/site_setting_extension.rb', line 122

def previews
  @previews ||= {}
end

#process_idObject



408
409
410
# File 'lib/site_setting_extension.rb', line 408

def process_id
  @process_id ||= SecureRandom.uuid
end

#process_message(message) ⇒ Object



399
400
401
402
403
404
405
406
# File 'lib/site_setting_extension.rb', line 399

def process_message(message)
  begin
    MessageBus.on_connect.call(message.site_id)
    refresh!
  ensure
    MessageBus.on_disconnect.call(message.site_id)
  end
end

#providerObject



65
66
67
# File 'lib/site_setting_extension.rb', line 65

def provider
  @provider ||= SiteSettings::DbProvider.new(SiteSetting)
end

#provider=(val) ⇒ Object



60
61
62
63
# File 'lib/site_setting_extension.rb', line 60

def provider=(val)
  @provider = val
  refresh!
end

#refresh!Object

refresh all the site settings



352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/site_setting_extension.rb', line 352

def refresh!
  mutex.synchronize do
    ensure_listen_for_changes

    new_hash =
      Hash[
        *(
          defaults
            .db_all
            .map do |s|
              [s.name.to_sym, type_supervisor.to_rb_value(s.name, s.value, s.data_type)]
            end
            .to_a
            .flatten
        )
      ]

    defaults_view = defaults.all(new_hash[:default_locale])

    # add locale default and defaults based on default_locale, cause they are cached
    new_hash = defaults_view.merge!(new_hash)

    # add shadowed
    shadowed_settings.each { |ss| new_hash[ss] = GlobalSetting.public_send(ss) }

    changes, deletions = diff_hash(new_hash, current)

    changes.each { |name, val| current[name] = val }
    deletions.each { |name, _| current[name] = defaults_view[name] }
    uploads.clear

    clear_cache!
  end
end

#refresh_settingsObject



114
115
116
# File 'lib/site_setting_extension.rb', line 114

def refresh_settings
  @refresh_settings ||= [:default_locale]
end

#remove_override!(name) ⇒ Object



417
418
419
420
421
422
423
424
425
426
427
428
429
# File 'lib/site_setting_extension.rb', line 417

def remove_override!(name)
  old_val = current[name]
  provider.destroy(name)
  current[name] = defaults.get(name, default_locale)

  return if current[name] == old_val

  clear_uploads_cache(name)
  clear_cache!
  if old_val != current[name]
    DiscourseEvent.trigger(:site_setting_changed, name, old_val, current[name])
  end
end

#requires_confirmation_settingsObject



102
103
104
# File 'lib/site_setting_extension.rb', line 102

def requires_confirmation_settings
  @requires_confirmation_settings ||= {}
end

#requires_refresh?(name) ⇒ Boolean

Returns:

  • (Boolean)


465
466
467
# File 'lib/site_setting_extension.rb', line 465

def requires_refresh?(name)
  refresh_settings.include?(name.to_sym)
end

#secret_settingsObject



126
127
128
# File 'lib/site_setting_extension.rb', line 126

def secret_settings
  @secret_settings ||= Set.new
end

#set(name, value, options = nil) ⇒ Object



494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
# File 'lib/site_setting_extension.rb', line 494

def set(name, value, options = nil)
  if has_setting?(name)
    value = filter_value(name, value)
    if options
      self.public_send("#{name}=", value, options)
    else
      self.public_send("#{name}=", value)
    end
    Discourse.request_refresh! if requires_refresh?(name)
  else
    raise Discourse::InvalidParameters.new(
            "Either no setting named '#{name}' exists or value provided is invalid",
          )
  end
end

#set_and_log(name, value, user = Discourse.system_user, detailed_message = nil) ⇒ Object



510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
# File 'lib/site_setting_extension.rb', line 510

def set_and_log(name, value, user = Discourse.system_user, detailed_message = nil)
  if has_setting?(name)
    prev_value = public_send(name)
    set(name, value)
    value = prev_value = "[FILTERED]" if secret_settings.include?(name.to_sym)
    StaffActionLogger.new(user).log_site_setting_change(
      name,
      prev_value,
      value,
      { details: detailed_message }.compact_blank,
    )
  else
    raise Discourse::InvalidParameters.new(
            I18n.t("errors.site_settings.invalid_site_setting", name: name),
          )
  end
end

#settings_hashObject



152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/site_setting_extension.rb', line 152

def settings_hash
  result = {}

  defaults.all.keys.each do |s|
    result[s] = if deprecated_settings.include?(s.to_s)
      public_send(s, warn: false).to_s
    else
      public_send(s).to_s
    end
  end

  result
end

#shadowed_settingsObject



98
99
100
# File 'lib/site_setting_extension.rb', line 98

def shadowed_settings
  @shadowed_settings ||= Set.new
end

#supported_typesObject



48
49
50
# File 'lib/site_setting_extension.rb', line 48

def supported_types
  SiteSettings::TypeSupervisor.supported_types
end

#type_supervisorObject



82
83
84
# File 'lib/site_setting_extension.rb', line 82

def type_supervisor
  @type_supervisor ||= SiteSettings::TypeSupervisor.new(defaults)
end

#typesObject



52
53
54
# File 'lib/site_setting_extension.rb', line 52

def types
  SiteSettings::TypeSupervisor.types
end

#valid_areasObject



554
555
556
# File 'lib/site_setting_extension.rb', line 554

def valid_areas
  Set.new(SiteSetting::VALID_AREAS | DiscoursePluginRegistry.site_setting_areas.to_a)
end