2013-03-01 17:21:13 -05:00
require_dependency 'enum'
2013-06-22 23:35:06 -04:00
require_dependency 'site_settings/db_provider'
2015-08-20 21:27:19 -04:00
require 'site_setting_validations'
2013-03-01 17:21:13 -05:00
2013-02-05 14:16:51 -05:00
module SiteSettingExtension
2015-08-20 21:27:19 -04:00
include SiteSettingValidations
2013-02-05 14:16:51 -05:00
2015-08-06 21:41:48 -04:00
# For plugins, so they can tell if a feature is supported
def supported_types
[ :email , :username , :list , :enum ]
end
2013-06-22 23:35:06 -04:00
# part 1 of refactor, centralizing the dependency here
def provider = ( val )
@provider = val
refresh!
end
def provider
@provider || = SiteSettings :: DbProvider . new ( SiteSetting )
end
2013-03-01 17:21:13 -05:00
def types
2016-01-08 05:53:52 -05:00
@types || = Enum . new ( string : 1 ,
time : 2 ,
2017-04-15 00:11:02 -04:00
integer : 3 ,
2016-01-08 05:53:52 -05:00
float : 4 ,
bool : 5 ,
null : 6 ,
enum : 7 ,
list : 8 ,
url_list : 9 ,
host_list : 10 ,
category_list : 11 ,
2016-11-08 16:36:34 -05:00
value_list : 12 ,
regex : 13 )
2013-02-05 14:16:51 -05:00
end
def mutex
@mutex || = Mutex . new
end
def current
2014-03-28 01:36:17 -04:00
@containers || = { }
@containers [ provider . current_site ] || = { }
2013-02-05 14:16:51 -05:00
end
def defaults
2013-02-25 11:42:20 -05:00
@defaults || = { }
2013-02-05 14:16:51 -05:00
end
2013-11-13 14:02:47 -05:00
def categories
@categories || = { }
end
2013-06-11 11:39:55 -04:00
def enums
@enums || = { }
end
2015-03-02 12:12:19 -05:00
def static_types
@static_types || = { }
2014-03-30 01:32:33 -04:00
end
2014-06-01 10:37:51 -04:00
def choices
@choices || = { }
end
2015-02-03 16:47:06 -05:00
def shadowed_settings
@shadowed_settings || = [ ]
end
2013-10-23 19:05:51 -04:00
def hidden_settings
@hidden_settings || = [ ]
end
2014-02-21 00:52:11 -05:00
def refresh_settings
@refresh_settings || = [ ]
end
2015-08-27 18:55:19 -04:00
def client_settings
@client_settings || = [ ]
end
2015-01-28 22:53:02 -05:00
def previews
@previews || = { }
end
2014-06-09 15:17:36 -04:00
def validators
@validators || = { }
end
2013-11-15 14:32:33 -05:00
def setting ( name_arg , default = nil , opts = { } )
2013-11-13 14:02:47 -05:00
name = name_arg . to_sym
2013-02-25 11:42:20 -05:00
mutex . synchronize do
2013-02-05 14:16:51 -05:00
self . defaults [ name ] = default
2013-11-15 14:32:33 -05:00
categories [ name ] = opts [ :category ] || :uncategorized
2013-02-05 14:16:51 -05:00
current_value = current . has_key? ( name ) ? current [ name ] : default
2015-02-06 06:08:37 -05:00
2015-03-02 12:12:19 -05:00
if enum = opts [ :enum ]
2013-06-22 23:35:06 -04:00
enums [ name ] = enum . is_a? ( String ) ? enum . constantize : enum
2015-03-02 12:12:19 -05:00
opts [ :type ] || = :enum
2013-06-22 23:35:06 -04:00
end
2015-02-06 06:08:37 -05:00
2015-03-13 01:15:13 -04:00
if new_choices = opts [ :choices ]
2015-09-09 08:34:44 -04:00
new_choices = eval ( new_choices ) if new_choices . is_a? ( String )
2015-03-13 01:15:13 -04:00
2014-06-01 10:37:51 -04:00
choices . has_key? ( name ) ?
2015-03-13 01:15:13 -04:00
choices [ name ] . concat ( new_choices ) :
choices [ name ] = new_choices
2014-06-01 10:37:51 -04:00
end
2015-02-06 06:08:37 -05:00
2015-03-02 12:12:19 -05:00
if type = opts [ :type ]
static_types [ name . to_sym ] = type . to_sym
2014-03-30 01:32:33 -04:00
end
2015-02-06 06:08:37 -05:00
2014-03-30 01:32:33 -04:00
if opts [ :hidden ]
2013-10-23 19:05:51 -04:00
hidden_settings << name
end
2015-01-28 22:53:02 -05:00
2015-02-03 16:47:06 -05:00
if opts [ :shadowed_by_global ] && GlobalSetting . respond_to? ( name )
2016-04-04 02:36:32 -04:00
val = GlobalSetting . send ( name )
2016-08-11 04:04:45 -04:00
2016-04-04 02:36:32 -04:00
unless val . nil? || ( val == '' . freeze )
2016-02-09 19:54:40 -05:00
hidden_settings << name
shadowed_settings << name
current_value = val
end
2015-02-03 16:47:06 -05:00
end
2014-03-30 01:32:33 -04:00
if opts [ :refresh ]
2014-02-21 00:52:11 -05:00
refresh_settings << name
end
2014-06-18 10:49:21 -04:00
2015-01-28 22:53:02 -05:00
if opts [ :preview ]
previews [ name ] = opts [ :preview ]
end
2015-02-06 06:08:37 -05:00
opts [ :validator ] = opts [ :validator ] . try ( :constantize )
type = opts [ :type ] || get_data_type ( name , defaults [ name ] )
if validator_type = opts [ :validator ] || validator_for ( type )
validators [ name ] = { class : validator_type , opts : opts }
2014-06-09 15:17:36 -04:00
end
2014-03-28 01:36:17 -04:00
current [ name ] = current_value
2015-02-03 16:47:06 -05:00
setup_methods ( name )
2013-02-05 14:16:51 -05:00
end
end
2013-02-25 11:42:20 -05:00
# just like a setting, except that it is available in javascript via DiscourseSession
2013-11-15 14:32:33 -05:00
def client_setting ( name , default = nil , opts = { } )
setting ( name , default , opts )
2016-02-10 22:16:09 -05:00
client_settings << name . to_sym
2013-02-05 14:16:51 -05:00
end
2013-04-05 15:21:55 -04:00
def settings_hash
result = { }
2014-08-14 17:54:55 -04:00
@defaults . each do | s , _ |
2013-04-05 15:21:55 -04:00
result [ s ] = send ( s ) . to_s
end
result
end
2013-02-05 14:16:51 -05:00
def client_settings_json
Rails . cache . fetch ( SiteSettingExtension . client_settings_cache_key , expires_in : 30 . minutes ) do
2013-10-09 19:32:03 -04:00
client_settings_json_uncached
2013-02-05 14:16:51 -05:00
end
end
2013-10-09 19:32:03 -04:00
def client_settings_json_uncached
2014-03-28 01:36:17 -04:00
MultiJson . dump ( Hash [ * @client_settings . map { | n | [ n , self . send ( n ) ] } . flatten ] )
2013-10-09 19:32:03 -04:00
end
2013-02-05 14:16:51 -05:00
# Retrieve all settings
2013-10-23 19:05:51 -04:00
def all_settings ( include_hidden = false )
@defaults
2015-02-03 16:47:06 -05:00
. reject { | s , _ | hidden_settings . include? ( s ) && ! include_hidden }
2013-10-23 19:05:51 -04:00
. map do | s , v |
value = send ( s )
type = types [ get_data_type ( s , value ) ]
2014-06-05 16:42:26 -04:00
opts = {
setting : s ,
description : description ( s ) ,
2014-08-20 15:24:56 -04:00
default : v . to_s ,
2014-06-05 16:42:26 -04:00
type : type . to_s ,
value : value . to_s ,
2015-01-28 22:53:02 -05:00
category : categories [ s ] ,
preview : previews [ s ]
2014-06-01 10:37:51 -04:00
}
2015-08-06 21:41:48 -04:00
if type == :enum && enum_class ( s )
opts . merge! ( { valid_values : enum_class ( s ) . values , translate_names : enum_class ( s ) . translate_names? } )
elsif type == :enum
opts . merge! ( { valid_values : choices [ s ] . map { | c | { name : c , value : c } } , translate_names : false } )
end
2014-06-01 10:37:51 -04:00
opts [ :choices ] = choices [ s ] if choices . has_key? s
opts
2013-10-23 19:05:51 -04:00
end
2013-02-05 14:16:51 -05:00
end
def description ( setting )
I18n . t ( " site_settings. #{ setting } " )
end
def self . client_settings_cache_key
2015-02-09 01:58:56 -05:00
# 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-05 14:16:51 -05:00
end
# refresh all the site settings
2013-02-25 11:42:20 -05:00
def refresh!
mutex . synchronize do
2013-02-05 14:16:51 -05:00
ensure_listen_for_changes
old = current
2015-09-09 06:59:49 -04:00
new_hash = Hash [ * ( provider . all . map { | s |
[ s . name . intern , convert ( s . value , s . data_type , s . name ) ]
2014-03-28 01:36:17 -04:00
} . to_a . flatten ) ]
2013-02-05 14:16:51 -05:00
2014-03-28 01:36:17 -04:00
# add defaults, cause they are cached
2013-02-05 14:16:51 -05:00
new_hash = defaults . merge ( new_hash )
2014-03-28 01:36:17 -04:00
2015-02-09 17:28:55 -05:00
# add shadowed
2015-09-09 06:59:49 -04:00
shadowed_settings . each { | ss | new_hash [ ss ] = GlobalSetting . send ( ss ) }
2015-02-09 17:28:55 -05:00
changes , deletions = diff_hash ( new_hash , old )
2015-09-09 06:59:49 -04:00
changes . each { | name , val | current [ name ] = val }
deletions . each { | name , val | current [ name ] = defaults [ name ] }
2015-02-09 17:28:55 -05:00
2014-03-28 01:36:17 -04:00
clear_cache!
2013-02-05 14:16:51 -05:00
end
end
def ensure_listen_for_changes
unless @subscribed
2015-05-03 22:21:00 -04:00
MessageBus . subscribe ( " /site_settings " ) do | message |
2013-06-12 22:41:27 -04:00
process_message ( message )
2013-02-05 14:16:51 -05:00
end
@subscribed = true
end
end
2013-06-12 22:41:27 -04:00
def process_message ( message )
data = message . data
if data [ " process " ] != process_id
begin
@last_message_processed = message . global_id
2015-05-03 22:21:00 -04:00
MessageBus . on_connect . call ( message . site_id )
2013-06-22 23:35:06 -04:00
refresh!
2013-06-12 22:41:27 -04:00
ensure
2015-05-03 22:21:00 -04:00
MessageBus . on_disconnect . call ( message . site_id )
2013-06-12 22:41:27 -04:00
end
end
end
2013-04-05 02:43:48 -04:00
def diags
{
last_message_processed : @last_message_processed
}
end
2013-02-05 14:16:51 -05:00
def process_id
2014-03-28 01:36:17 -04:00
@process_id || = SecureRandom . uuid
2013-02-05 14:16:51 -05:00
end
2014-03-27 22:48:14 -04:00
def after_fork
2014-03-28 01:36:17 -04:00
@process_id = nil
ensure_listen_for_changes
2014-03-27 22:48:14 -04:00
end
2013-02-05 14:16:51 -05:00
def remove_override! ( name )
2013-06-22 23:35:06 -04:00
provider . destroy ( name )
current [ name ] = defaults [ name ]
2014-03-28 01:36:17 -04:00
clear_cache!
2013-02-05 14:16:51 -05:00
end
2015-03-02 12:12:19 -05:00
def add_override! ( name , val )
2015-09-09 08:34:44 -04:00
type = get_data_type ( name , defaults [ name . to_sym ] )
2013-02-25 11:42:20 -05:00
2013-03-01 17:21:13 -05:00
if type == types [ :bool ] && val != true && val != false
2013-02-27 11:19:09 -05:00
val = ( val == " t " || val == " true " ) ? 't' : 'f'
2013-02-05 14:16:51 -05:00
end
2017-04-15 00:11:02 -04:00
if type == types [ :integer ] && ! val . is_a? ( Integer )
2013-02-05 14:16:51 -05:00
val = val . to_i
end
2013-03-01 17:21:13 -05:00
if type == types [ :null ] && val != ''
2013-06-11 11:39:55 -04:00
type = get_data_type ( name , val )
end
if type == types [ :enum ]
2017-04-15 00:11:02 -04:00
val = val . to_i if defaults [ name . to_sym ] . is_a? ( Integer )
2015-08-06 21:41:48 -04:00
if enum_class ( name )
raise Discourse :: InvalidParameters . new ( :value ) unless enum_class ( name ) . valid_value? ( val )
else
raise Discourse :: InvalidParameters . new ( :value ) unless choices [ name ] . include? ( val )
end
2013-02-27 19:24:43 -05:00
end
2014-06-12 18:03:03 -04:00
if v = validators [ name ]
validator = v [ :class ] . new ( v [ :opts ] )
unless validator . valid_value? ( val )
2014-06-18 10:49:21 -04:00
raise Discourse :: InvalidParameters . new ( validator . error_message )
2014-06-12 18:03:03 -04:00
end
2014-06-09 15:17:36 -04:00
end
2015-08-20 21:27:19 -04:00
if self . respond_to? " validate_ #{ name } "
send ( " validate_ #{ name } " , val )
end
2013-06-22 23:35:06 -04:00
provider . save ( name , val , type )
2015-09-09 06:59:49 -04:00
current [ name ] = convert ( val , type , name )
2015-08-27 18:55:19 -04:00
notify_clients! ( name ) if client_settings . include? name
2014-03-28 01:36:17 -04:00
clear_cache!
2014-03-30 21:34:01 -04:00
end
2014-03-28 01:36:17 -04:00
2014-03-30 21:34:01 -04:00
def notify_changed!
2015-05-03 22:21:00 -04:00
MessageBus . publish ( '/site_settings' , { process : process_id } )
2013-02-05 14:16:51 -05:00
end
2015-08-27 18:55:19 -04:00
def notify_clients! ( name )
MessageBus . publish ( '/client_settings' , { name : name , value : self . send ( name ) } )
end
2014-01-27 13:05:35 -05:00
def has_setting? ( name )
defaults . has_key? ( name . to_sym ) || defaults . has_key? ( " #{ name } ? " . to_sym )
end
2014-02-21 00:52:11 -05:00
def requires_refresh? ( name )
refresh_settings . include? ( name . to_sym )
end
2015-04-23 08:40:12 -04:00
def is_valid_data? ( name , value )
valid = true
type = get_data_type ( name , defaults [ name . to_sym ] )
2017-04-15 00:11:02 -04:00
if type == types [ :integer ]
# validate integer
valid = false unless value . to_i . is_a? ( Integer )
2015-04-23 08:40:12 -04:00
end
2015-08-26 16:40:16 -04:00
valid
2015-04-23 08:40:12 -04:00
end
2014-07-24 08:00:15 -04:00
def filter_value ( name , value )
2016-10-24 06:46:22 -04:00
if %w[ disabled_image_download_domains onebox_domains_blacklist exclude_rel_nofollow_domains email_domains_blacklist email_domains_whitelist white_listed_spam_host_domains ] . include? name
2014-07-24 08:00:15 -04:00
domain_array = [ ]
2016-10-24 06:46:22 -04:00
value . split ( '|' ) . each { | url | domain_array << get_hostname ( url ) }
2014-07-24 08:00:15 -04:00
value = domain_array . join ( " | " )
end
2014-12-11 11:08:47 -05:00
value
2014-07-24 08:00:15 -04:00
end
2014-01-27 13:05:35 -05:00
def set ( name , value )
2015-04-23 08:40:12 -04:00
if has_setting? ( name ) && is_valid_data? ( name , value )
2014-07-24 08:00:15 -04:00
value = filter_value ( name , value )
2014-01-27 13:05:35 -05:00
self . send ( " #{ name } = " , value )
2014-02-21 00:52:11 -05:00
Discourse . request_refresh! if requires_refresh? ( name )
2014-01-27 13:05:35 -05:00
else
2015-04-23 08:40:12 -04:00
raise ArgumentError . new ( " Either no setting named ' #{ name } ' exists or value provided is invalid " )
2014-01-27 13:05:35 -05:00
end
end
2016-04-26 13:08:19 -04:00
def set_and_log ( name , value , user = Discourse . system_user )
prev_value = send ( name )
set ( name , value )
StaffActionLogger . new ( user ) . log_site_setting_change ( name , prev_value , value ) if has_setting? ( name )
end
2013-02-25 11:42:20 -05:00
protected
2013-02-05 14:16:51 -05:00
2014-03-28 01:36:17 -04:00
def clear_cache!
Rails . cache . delete ( SiteSettingExtension . client_settings_cache_key )
2015-09-28 02:44:03 -04:00
Site . clear_anon_cache!
2014-03-28 01:36:17 -04:00
end
2013-06-12 22:41:27 -04:00
def diff_hash ( new_hash , old )
changes = [ ]
deletions = [ ]
new_hash . each do | name , value |
changes << [ name , value ] if ! old . has_key? ( name ) || old [ name ] != value
end
old . each do | name , value |
deletions << [ name , value ] unless new_hash . has_key? ( name )
end
[ changes , deletions ]
end
2015-03-02 12:12:19 -05:00
def get_data_type ( name , val )
2013-03-01 17:21:13 -05:00
return types [ :null ] if val . nil?
2015-03-02 12:12:19 -05:00
2015-09-09 08:34:44 -04:00
# Some types are just for validations like email.
# Only consider it valid if includes in `types`
2015-03-02 12:12:19 -05:00
if static_type = static_types [ name . to_sym ]
return types [ static_type ] if types . keys . include? ( static_type )
end
2013-02-05 14:16:51 -05:00
2013-06-12 22:41:27 -04:00
case val
when String
2013-03-01 17:21:13 -05:00
types [ :string ]
2017-04-15 00:11:02 -04:00
when Integer
types [ :integer ]
2014-08-15 12:02:29 -04:00
when Float
types [ :float ]
2013-06-12 22:41:27 -04:00
when TrueClass , FalseClass
2013-03-01 17:21:13 -05:00
types [ :bool ]
2013-02-25 11:42:20 -05:00
else
2013-02-05 14:16:51 -05:00
raise ArgumentError . new :val
end
end
2015-09-09 06:59:49 -04:00
def convert ( value , type , name )
2013-02-25 11:42:20 -05:00
case type
2014-08-20 15:24:56 -04:00
when types [ :float ]
value . to_f
2017-04-15 00:11:02 -04:00
when types [ :integer ]
2013-02-05 14:16:51 -05:00
value . to_i
2013-03-01 17:21:13 -05:00
when types [ :bool ]
2013-06-22 23:35:06 -04:00
value == true || value == " t " || value == " true "
2013-03-01 17:21:13 -05:00
when types [ :null ]
2013-02-05 14:16:51 -05:00
nil
2015-09-09 06:59:49 -04:00
when types [ :enum ]
2017-04-15 00:11:02 -04:00
defaults [ name . to_sym ] . is_a? ( Integer ) ? value . to_i : value
2014-03-30 01:32:33 -04:00
else
2015-03-02 12:12:19 -05:00
return value if types [ type ]
# Otherwise it's a type error
2014-03-30 01:32:33 -04:00
raise ArgumentError . new :type
2013-02-05 14:16:51 -05:00
end
end
2014-06-12 18:03:03 -04:00
def validator_for ( type_name )
@validator_mapping || = {
2014-06-18 10:49:21 -04:00
'email' = > EmailSettingValidator ,
'username' = > UsernameSettingValidator ,
2017-04-15 00:11:02 -04:00
types [ :integer ] = > IntegerSettingValidator ,
2015-02-26 19:45:56 -05:00
types [ :string ] = > StringSettingValidator ,
2015-08-06 21:41:48 -04:00
'list' = > StringSettingValidator ,
2016-11-08 16:36:34 -05:00
'enum' = > StringSettingValidator ,
'regex' = > RegexSettingValidator
2014-06-12 18:03:03 -04:00
}
@validator_mapping [ type_name ]
end
2016-06-27 05:26:43 -04:00
DEPRECATED_SETTINGS = [
[ 'use_https' , 'force_https' , '1.7' ]
]
def setup_deprecated_methods
DEPRECATED_SETTINGS . each do | old_setting , new_setting , version |
define_singleton_method old_setting do
2016-06-30 13:18:57 -04:00
logger . warn ( " `SiteSetting. #{ old_setting } ` has been deprecated and will be removed in the #{ version } Release. Please use `SiteSetting. #{ new_setting } ` instead " )
2016-06-27 05:26:43 -04:00
self . public_send new_setting
end
define_singleton_method " #{ old_setting } ? " do
2016-06-30 13:18:57 -04:00
logger . warn ( " `SiteSetting. #{ old_setting } ?` has been deprecated and will be removed in the #{ version } Release. Please use `SiteSetting. #{ new_setting } ?` instead " )
2016-06-27 05:26:43 -04:00
self . public_send " #{ new_setting } ? "
end
define_singleton_method " #{ old_setting } = " do | val |
2016-06-30 13:18:57 -04:00
logger . warn ( " `SiteSetting. #{ old_setting } =` has been deprecated and will be removed in the #{ version } Release. Please use `SiteSetting. #{ new_setting } =` instead " )
2016-06-27 05:26:43 -04:00
self . public_send " #{ new_setting } = " , val
end
end
end
2013-02-05 14:16:51 -05:00
2015-02-03 16:47:06 -05:00
def setup_methods ( name )
2015-02-11 23:07:17 -05:00
clean_name = name . to_s . sub ( " ? " , " " ) . to_sym
2013-02-05 14:16:51 -05:00
2015-02-11 22:44:40 -05:00
define_singleton_method clean_name do
2014-03-28 01:36:17 -04:00
c = @containers [ provider . current_site ]
if c
c [ name ]
else
refresh!
current [ name ]
end
2013-09-03 03:39:56 -04:00
end
2013-02-05 14:16:51 -05:00
2015-02-11 23:07:17 -05:00
define_singleton_method " #{ clean_name } ? " do
2015-02-11 23:01:14 -05:00
self . send clean_name
2013-02-05 14:16:51 -05:00
end
2013-02-25 11:42:20 -05:00
2015-02-11 23:07:17 -05:00
define_singleton_method " #{ clean_name } = " do | val |
2015-02-11 22:44:40 -05:00
add_override! ( name , val )
2013-02-05 14:16:51 -05:00
end
end
2013-06-11 11:39:55 -04:00
def enum_class ( name )
enums [ name ]
end
2013-02-05 14:16:51 -05:00
2014-07-24 08:00:15 -04:00
def get_hostname ( url )
unless ( URI . parse ( url ) . scheme rescue nil ) . nil?
url = " http:// #{ url } " if URI . parse ( url ) . scheme . nil?
url = URI . parse ( url ) . host
end
2014-12-11 11:08:47 -05:00
url
2014-07-24 08:00:15 -04:00
end
2016-06-27 05:26:43 -04:00
private
def logger
Rails . logger
end
2013-02-05 14:16:51 -05:00
end