discourse/lib/site_setting_extension.rb

488 lines
11 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
require_dependency 'site_settings/deprecated_settings'
require_dependency 'site_settings/type_supervisor'
require_dependency 'site_settings/defaults_provider'
require_dependency 'site_settings/db_provider'
2013-02-06 03:16:51 +08:00
module SiteSettingExtension
include SiteSettings::DeprecatedSettings
2013-02-06 03:16:51 +08:00
# support default_locale being set via global settings
# this also adds support for testing the extension and global settings
# for site locale
def self.extended(klass)
if GlobalSetting.respond_to?(:default_locale) && GlobalSetting.default_locale.present?
klass.send :setup_shadowed_methods, :default_locale, GlobalSetting.default_locale
end
end
# we need a default here to support defaults per locale
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
def default_locale?
true
end
# set up some sort of default so we can look stuff up
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
def has_setting?(v)
defaults.has_setting?(v)
end
def supported_types
SiteSettings::TypeSupervisor.supported_types
end
def types
SiteSettings::TypeSupervisor.types
end
def listen_for_changes=(val)
@listen_for_changes = val
end
def provider=(val)
@provider = val
refresh!
end
def provider
@provider ||= SiteSettings::DbProvider.new(SiteSetting)
end
2013-02-06 03:16:51 +08:00
def mutex
@mutex ||= Mutex.new
end
def current
@containers ||= {}
@containers[provider.current_site] ||= {}
2013-02-06 03:16:51 +08:00
end
def defaults
@defaults ||= SiteSettings::DefaultsProvider.new(self)
2013-11-14 03:02:47 +08:00
end
def type_supervisor
@type_supervisor ||= SiteSettings::TypeSupervisor.new(defaults)
end
def categories
@categories ||= {}
end
def shadowed_settings
@shadowed_settings ||= []
end
def hidden_settings
@hidden_settings ||= []
end
def refresh_settings
@refresh_settings ||= [:default_locale]
end
2015-08-28 06:55:19 +08:00
def client_settings
@client_settings ||= [:default_locale]
2015-08-28 06:55:19 +08:00
end
def previews
@previews ||= {}
end
def secret_settings
@secret_settings ||= []
end
def setting(name_arg, default = nil, opts = {})
2013-11-14 03:02:47 +08:00
name = name_arg.to_sym
if name == :default_locale
raise Discourse::InvalidParameters.new("Other settings depend on default locale, you can not configure it like this")
end
shadowed_val = nil
2013-02-26 00:42:20 +08:00
mutex.synchronize do
defaults.load_setting(
name,
default,
opts.delete(:locale_default)
)
categories[name] = opts[:category] || :uncategorized
2014-03-30 13:32:33 +08:00
if opts[:hidden]
hidden_settings << name
end
if opts[:shadowed_by_global] && GlobalSetting.respond_to?(name)
val = GlobalSetting.send(name)
unless val.nil? || (val == ''.freeze)
shadowed_val = val
hidden_settings << name
shadowed_settings << name
end
end
2014-03-30 13:32:33 +08:00
if opts[:refresh]
refresh_settings << name
end
if opts[:client]
client_settings << name.to_sym
end
if opts[:preview]
previews[name] = opts[:preview]
end
if opts[:secret]
secret_settings << name
end
type_supervisor.load_setting(
name,
opts.extract!(*SiteSettings::TypeSupervisor::CONSUMED_OPTS)
)
if !shadowed_val.nil?
setup_shadowed_methods(name, shadowed_val)
else
setup_methods(name)
end
2013-02-06 03:16:51 +08:00
end
end
def settings_hash
result = {}
defaults.all.keys.each do |s|
result[s] = send(s).to_s
end
result
end
2013-02-06 03:16:51 +08:00
def client_settings_json
Rails.cache.fetch(SiteSettingExtension.client_settings_cache_key, expires_in: 30.minutes) do
client_settings_json_uncached
2013-02-06 03:16:51 +08:00
end
end
def client_settings_json_uncached
2018-11-14 15:03:02 +08:00
MultiJson.dump(Hash[*@client_settings.map do |name|
value = self.public_send(name)
value = value.to_s if type_supervisor.get_type(name) == :upload
[name, value]
end.flatten])
end
2013-02-06 03:16:51 +08:00
# Retrieve all settings
2017-07-28 09:20:09 +08:00
def all_settings(include_hidden = false)
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?
}
defaults.all(default_locale)
.reject { |s, _| !include_hidden && hidden_settings.include?(s) }
.map do |s, v|
2018-11-14 15:03:02 +08:00
value = send(s)
2018-11-14 15:03:02 +08:00
opts = {
setting: s,
description: description(s),
default: defaults.get(s, default_locale).to_s,
value: value.to_s,
category: categories[s],
preview: previews[s],
secret: secret_settings.include?(s),
placeholder: placeholder(s)
}.merge(type_supervisor.type_hash(s))
opts
end.unshift(locale_setting_hash)
2013-02-06 03:16:51 +08:00
end
def description(setting)
I18n.t("site_settings.#{setting}", base_path: Discourse.base_path)
2013-02-06 03:16:51 +08:00
end
def placeholder(setting)
if !I18n.t("site_settings.placeholder.#{setting}", default: "").empty?
I18n.t("site_settings.placeholder.#{setting}")
end
end
2013-02-06 03:16:51 +08:00
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}"
2013-02-06 03:16:51 +08:00
end
# refresh all the site settings
2013-02-26 00:42:20 +08:00
def refresh!
mutex.synchronize do
2013-02-06 03:16:51 +08:00
ensure_listen_for_changes
new_hash = Hash[*(defaults.db_all.map { |s|
[s.name.to_sym, type_supervisor.to_rb_value(s.name, s.value, s.data_type)]
}.to_a.flatten)]
2013-02-06 03:16:51 +08:00
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.send(ss) }
changes, deletions = diff_hash(new_hash, current)
changes.each { |name, val| current[name] = val }
deletions.each { |name, _| current[name] = defaults_view[name] }
clear_cache!
2013-02-06 03:16:51 +08:00
end
end
def ensure_listen_for_changes
return if @listen_for_changes == false
2013-02-06 03:16:51 +08:00
unless @subscribed
MessageBus.subscribe("/site_settings") do |message|
2013-06-13 10:41:27 +08:00
process_message(message)
2013-02-06 03:16:51 +08:00
end
@subscribed = true
end
end
2013-06-13 10:41:27 +08:00
def process_message(message)
data = message.data
if data["process"] != process_id
begin
@last_message_processed = message.global_id
MessageBus.on_connect.call(message.site_id)
refresh!
2013-06-13 10:41:27 +08:00
ensure
MessageBus.on_disconnect.call(message.site_id)
2013-06-13 10:41:27 +08:00
end
end
end
def diags
{
last_message_processed: @last_message_processed
}
end
2013-02-06 03:16:51 +08:00
def process_id
@process_id ||= SecureRandom.uuid
2013-02-06 03:16:51 +08:00
end
def after_fork
@process_id = nil
ensure_listen_for_changes
end
2013-02-06 03:16:51 +08:00
def remove_override!(name)
provider.destroy(name)
current[name] = defaults.get(name, default_locale)
2018-11-14 15:03:02 +08:00
uploads.delete(name)
clear_cache!
2013-02-06 03:16:51 +08:00
end
2015-03-03 01:12:19 +08:00
def add_override!(name, val)
val, type = type_supervisor.to_db_value(name, val)
provider.save(name, val, type)
current[name] = type_supervisor.to_rb_value(name, val)
2018-11-14 15:03:02 +08:00
uploads.delete(name)
2015-08-28 06:55:19 +08:00
notify_clients!(name) if client_settings.include? name
clear_cache!
end
def notify_changed!
2017-07-28 09:20:09 +08:00
MessageBus.publish('/site_settings', process: process_id)
2013-02-06 03:16:51 +08:00
end
2015-08-28 06:55:19 +08:00
def notify_clients!(name)
2017-07-28 09:20:09 +08:00
MessageBus.publish('/client_settings', name: name, value: self.send(name))
2015-08-28 06:55:19 +08:00
end
def requires_refresh?(name)
refresh_settings.include?(name.to_sym)
end
HOSTNAME_SETTINGS ||= %w{
disabled_image_download_domains onebox_domains_blacklist exclude_rel_nofollow_domains
email_domains_blacklist email_domains_whitelist white_listed_spam_host_domains
}
2014-07-24 20:00:15 +08:00
def filter_value(name, value)
if HOSTNAME_SETTINGS.include?(name)
value.split("|").map { |url| get_hostname(url) }.compact.uniq.join("|")
else
value
2014-07-24 20:00:15 +08:00
end
end
2014-01-28 02:05:35 +08:00
def set(name, value)
if has_setting?(name)
2014-07-24 20:00:15 +08:00
value = filter_value(name, value)
2014-01-28 02:05:35 +08:00
self.send("#{name}=", value)
Discourse.request_refresh! if requires_refresh?(name)
2014-01-28 02:05:35 +08:00
else
raise Discourse::InvalidParameters.new("Either no setting named '#{name}' exists or value provided is invalid")
2014-01-28 02:05:35 +08:00
end
end
2017-07-28 09:20:09 +08:00
def set_and_log(name, value, user = Discourse.system_user)
prev_value = send(name)
set(name, value)
if has_setting?(name)
value = prev_value = "[FILTERED]" if secret_settings.include?(name.to_sym)
StaffActionLogger.new(user).log_site_setting_change(name, prev_value, value)
end
end
2013-02-26 00:42:20 +08:00
protected
2013-02-06 03:16:51 +08:00
def clear_cache!
Rails.cache.delete(SiteSettingExtension.client_settings_cache_key)
Site.clear_anon_cache!
end
2013-06-13 10:41:27 +08:00
def diff_hash(new_hash, old)
changes = []
deletions = []
new_hash.each do |name, value|
2017-07-28 09:20:09 +08:00
changes << [name, value] if !old.has_key?(name) || old[name] != value
2013-06-13 10:41:27 +08:00
end
2017-07-28 09:20:09 +08:00
old.each do |name, value|
deletions << [name, value] unless new_hash.has_key?(name)
2013-06-13 10:41:27 +08:00
end
2017-07-28 09:20:09 +08:00
[changes, deletions]
2013-06-13 10:41:27 +08:00
end
def setup_shadowed_methods(name, value)
clean_name = name.to_s.sub("?", "").to_sym
define_singleton_method clean_name do
value
end
define_singleton_method "#{clean_name}?" do
value
end
define_singleton_method "#{clean_name}=" do |val|
Rails.logger.warn("An attempt was to change #{clean_name} SiteSetting to #{val} however it is shadowed so this will be ignored!")
nil
end
end
def setup_methods(name)
2015-02-12 12:07:17 +08:00
clean_name = name.to_s.sub("?", "").to_sym
2013-02-06 03:16:51 +08:00
2018-11-14 15:03:02 +08:00
if type_supervisor.get_type(name) == :upload
define_singleton_method clean_name do
upload = uploads[name]
return upload if upload
if (value = current[name]).nil?
refresh!
value = current[name]
end
value = value.to_i
if value > 0
upload = Upload.find_by(id: value)
uploads[name] = upload if upload
end
end
else
define_singleton_method clean_name do
if (c = current[name]).nil?
refresh!
current[name]
else
c
end
end
end
2013-02-06 03:16:51 +08:00
2015-02-12 12:07:17 +08:00
define_singleton_method "#{clean_name}?" do
self.send clean_name
2013-02-06 03:16:51 +08:00
end
2013-02-26 00:42:20 +08:00
2015-02-12 12:07:17 +08:00
define_singleton_method "#{clean_name}=" do |val|
add_override!(name, val)
2013-02-06 03:16:51 +08:00
end
end
2014-07-24 20:00:15 +08:00
def get_hostname(url)
url.strip!
host = begin
URI.parse(url)&.host
rescue URI::Error
nil
2014-07-24 20:00:15 +08:00
end
host ||= begin
URI.parse("http://#{url}")&.host
rescue URI::Error
nil
end
host.presence || url
2014-07-24 20:00:15 +08:00
end
private
2018-11-14 15:03:02 +08:00
def uploads
@uploads ||= {}
@uploads[provider.current_site] ||= {}
end
def logger
Rails.logger
end
2013-02-06 03:16:51 +08:00
end