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