2013-05-31 08:41:29 +10:00
require 'cache'
2013-08-23 16:21:52 +10:00
require_dependency 'plugin/instance'
2013-10-09 15:10:37 +11:00
require_dependency 'auth/default_current_user_provider'
2015-04-27 13:06:53 -04:00
require_dependency 'version'
2013-05-31 08:41:29 +10:00
2015-07-14 14:52:35 -04:00
# Prevents errors with reloading dev with conditional includes
if Rails . env . development?
require_dependency 'file_store/s3_store'
require_dependency 'file_store/local_store'
end
2013-02-05 14:16:51 -05:00
module Discourse
2014-04-17 15:57:17 +10:00
require 'sidekiq/exception_handler'
2014-02-21 14:30:25 +11:00
class SidekiqExceptionHandler
extend Sidekiq :: ExceptionHandler
end
2014-07-17 13:22:46 -07:00
# Log an exception.
#
2014-07-17 15:07:25 -07:00
# If your code is in a scheduled job, it is recommended to use the
# error_context() method in Jobs::Base to pass the job arguments and any
# other desired context.
2014-07-17 13:22:46 -07:00
# See app/jobs/base.rb for the error_context function.
2015-02-09 12:47:46 -08:00
def self . handle_job_exception ( ex , context = { } , parent_logger = nil )
2014-02-21 14:30:25 +11:00
context || = { }
parent_logger || = SidekiqExceptionHandler
cm = RailsMultisite :: ConnectionManagement
parent_logger . handle_exception ( ex , {
current_db : cm . current_db ,
current_hostname : cm . current_hostname
} . merge ( context ) )
end
2013-06-19 10:31:19 +10:00
# Expected less matches than what we got in a find
2015-03-23 12:16:21 +11:00
class TooManyMatches < StandardError ; end
2013-06-19 10:31:19 +10:00
2013-02-25 19:42:20 +03:00
# When they try to do something they should be logged in for
2015-03-23 12:16:21 +11:00
class NotLoggedIn < StandardError ; end
2013-02-05 14:16:51 -05:00
# When the input is somehow bad
2015-03-23 12:16:21 +11:00
class InvalidParameters < StandardError ; end
2013-02-05 14:16:51 -05:00
# When they don't have permission to do something
2015-09-18 00:14:10 -07:00
class InvalidAccess < StandardError
attr_reader :obj
def initialize ( msg = nil , obj = nil )
super ( msg )
@obj = obj
end
end
2013-02-05 14:16:51 -05:00
# When something they want is not found
2015-03-23 12:16:21 +11:00
class NotFound < StandardError ; end
2013-02-05 14:16:51 -05:00
2013-06-05 00:34:53 +02:00
# When a setting is missing
2015-03-23 12:16:21 +11:00
class SiteSettingMissing < StandardError ; end
2013-06-05 00:34:53 +02:00
2013-11-05 19:04:47 +01:00
# When ImageMagick is missing
2015-03-23 12:16:21 +11:00
class ImageMagickMissing < StandardError ; end
2013-11-05 19:04:47 +01:00
2014-02-12 20:37:28 -08:00
# When read-only mode is enabled
2015-03-23 12:16:21 +11:00
class ReadOnly < StandardError ; end
2014-02-12 20:37:28 -08:00
2013-07-29 15:13:13 +10:00
# Cross site request forgery
2015-03-23 12:16:21 +11:00
class CSRF < StandardError ; end
2013-07-29 15:13:13 +10:00
2013-12-24 00:50:36 +01:00
def self . filters
2015-07-27 16:46:50 +10:00
@filters || = [ :latest , :unread , :new , :read , :posted , :bookmarks ]
2013-12-24 00:50:36 +01:00
end
def self . anonymous_filters
2015-07-27 16:46:50 +10:00
@anonymous_filters || = [ :latest , :top , :categories ]
2013-12-24 00:50:36 +01:00
end
def self . top_menu_items
2014-01-14 12:48:57 -05:00
@top_menu_items || = Discourse . filters + [ :category , :categories , :top ]
2013-12-24 00:50:36 +01:00
end
def self . anonymous_top_menu_items
2014-01-14 12:48:57 -05:00
@anonymous_top_menu_items || = Discourse . anonymous_filters + [ :category , :categories , :top ]
2013-12-24 00:50:36 +01:00
end
2016-04-06 10:57:59 +02:00
PIXEL_RATIOS || = [ 1 , 1 . 5 , 2 , 3 ]
2015-05-29 09:57:54 +02:00
2015-05-25 17:59:00 +02:00
def self . avatar_sizes
2015-05-29 09:57:54 +02:00
# TODO: should cache these when we get a notification system for site settings
set = Set . new
SiteSetting . avatar_sizes . split ( " | " ) . map ( & :to_i ) . each do | size |
PIXEL_RATIOS . each do | pixel_ratio |
set << size * pixel_ratio
end
end
2015-05-26 15:41:50 +10:00
set
2015-05-25 17:59:00 +02:00
end
2013-08-01 15:59:57 +10:00
def self . activate_plugins!
2015-04-27 13:06:53 -04:00
all_plugins = Plugin :: Instance . find_all ( " #{ Rails . root } /plugins " )
@plugins = [ ]
all_plugins . each do | p |
v = p . metadata . required_version || Discourse :: VERSION :: STRING
if Discourse . has_needed_version? ( Discourse :: VERSION :: STRING , v )
p . activate!
@plugins << p
else
STDERR . puts " Could not activate #{ p . metadata . name } , discourse does not meet required version ( #{ v } ) "
end
end
2013-08-01 15:59:57 +10:00
end
2016-02-29 18:58:42 +08:00
def self . last_read_only
@last_read_only || = { }
end
2015-04-29 11:49:58 -04:00
def self . recently_readonly?
2016-02-29 18:58:42 +08:00
read_only = last_read_only [ $redis . namespace ]
return false unless read_only
read_only > 15 . seconds . ago
2015-04-29 11:49:58 -04:00
end
def self . received_readonly!
2016-02-29 18:58:42 +08:00
last_read_only [ $redis . namespace ] = Time . zone . now
2015-04-29 11:49:58 -04:00
end
def self . clear_readonly!
2016-02-29 18:58:42 +08:00
last_read_only [ $redis . namespace ] = nil
2015-04-29 11:49:58 -04:00
end
2015-02-04 16:23:39 -05:00
def self . disabled_plugin_names
2016-06-30 16:55:01 +02:00
plugins . select { | p | ! p . enabled? } . map ( & :name )
2015-02-04 16:23:39 -05:00
end
2013-08-01 15:59:57 +10:00
def self . plugins
2015-02-10 11:18:16 -05:00
@plugins || = [ ]
2013-08-01 15:59:57 +10:00
end
2014-01-15 12:07:42 +11:00
def self . assets_digest
@assets_digest || = begin
digest = Digest :: MD5 . hexdigest ( ActionView :: Base . assets_manifest . assets . values . sort . join )
channel = " /global/asset-version "
2015-05-04 12:21:00 +10:00
message = MessageBus . last_message ( channel )
2014-01-15 12:07:42 +11:00
unless message && message . data == digest
2015-05-04 12:21:00 +10:00
MessageBus . publish channel , digest
2014-01-15 12:07:42 +11:00
end
digest
end
end
2013-08-26 11:04:16 +10:00
def self . authenticators
# TODO: perhaps we don't need auth providers and authenticators maybe one object is enough
# NOTE: this bypasses the site settings and gives a list of everything, we need to register every middleware
# for the cases of multisite
# In future we may change it so we don't include them all for cases where we are not a multisite, but we would
# require a restart after site settings change
Users :: OmniauthCallbacksController :: BUILTIN_AUTH + auth_providers . map ( & :authenticator )
end
2013-08-01 15:59:57 +10:00
def self . auth_providers
2013-08-01 16:05:46 +10:00
providers = [ ]
2015-02-10 11:18:16 -05:00
plugins . each do | p |
next unless p . auth_providers
p . auth_providers . each do | prov |
providers << prov
2013-08-01 15:59:57 +10:00
end
end
providers
end
2013-05-31 08:41:29 +10:00
def self . cache
@cache || = Cache . new
end
2013-02-05 14:16:51 -05:00
# Get the current base URL for the current site
def self . current_hostname
2016-06-30 16:55:01 +02:00
SiteSetting . force_hostname . presence || RailsMultisite :: ConnectionManagement . current_hostname
2013-05-31 08:41:29 +10:00
end
2013-11-05 19:04:47 +01:00
def self . base_uri ( default_value = " " )
2016-06-30 16:55:01 +02:00
ActionController :: Base . config . relative_url_root . presence || default_value
2013-03-14 13:01:52 +01:00
end
2016-07-28 13:54:17 -04:00
def self . base_protocol
SiteSetting . force_https? ? " https " : " http "
end
2013-05-31 08:41:29 +10:00
def self . base_url_no_prefix
2016-07-28 13:54:17 -04:00
default_port = SiteSetting . force_https? ? 443 : 80
url = " #{ base_protocol } :// #{ current_hostname } "
2016-06-30 16:55:01 +02:00
url << " : #{ SiteSetting . port } " if SiteSetting . port . to_i > 0 && SiteSetting . port . to_i != default_port
url
2013-04-05 12:38:20 +02:00
end
2013-05-31 08:41:29 +10:00
def self . base_url
2015-09-21 20:28:20 +02:00
base_url_no_prefix + base_uri
2013-05-31 08:41:29 +10:00
end
2016-06-29 13:55:17 +08:00
READONLY_MODE_KEY_TTL || = 60
2016-06-29 14:19:18 +08:00
READONLY_MODE_KEY || = 'readonly_mode' . freeze
USER_READONLY_MODE_KEY || = 'readonly_mode:user' . freeze
def self . enable_readonly_mode ( user_enabled : false )
if user_enabled
$redis . set ( USER_READONLY_MODE_KEY , 1 )
else
$redis . setex ( READONLY_MODE_KEY , READONLY_MODE_KEY_TTL , 1 )
keep_readonly_mode
end
2016-06-29 13:55:17 +08:00
2015-05-04 12:21:00 +10:00
MessageBus . publish ( readonly_channel , true )
2013-02-05 14:16:51 -05:00
true
end
2015-02-11 21:50:17 +01:00
def self . keep_readonly_mode
# extend the expiry by 1 minute every 30 seconds
Thread . new do
while readonly_mode?
2016-06-29 14:19:18 +08:00
$redis . expire ( READONLY_MODE_KEY , READONLY_MODE_KEY_TTL )
2015-02-11 21:50:17 +01:00
sleep 30 . seconds
end
end
end
2016-06-29 14:19:18 +08:00
def self . disable_readonly_mode ( user_enabled : false )
key = user_enabled ? USER_READONLY_MODE_KEY : READONLY_MODE_KEY
$redis . del ( key )
2015-05-04 12:21:00 +10:00
MessageBus . publish ( readonly_channel , false )
2013-02-05 14:16:51 -05:00
true
end
2014-02-12 20:37:28 -08:00
def self . readonly_mode?
2016-08-25 23:30:41 +08:00
recently_readonly? || ! ! $redis . get ( READONLY_MODE_KEY ) || ! ! $redis . get ( USER_READONLY_MODE_KEY )
2013-02-05 14:16:51 -05:00
end
2014-02-20 21:52:11 -08:00
def self . request_refresh!
# Causes refresh on next click for all clients
#
2015-05-04 12:21:00 +10:00
# This is better than `MessageBus.publish "/file-change", ["refresh"]` because
2014-02-20 21:52:11 -08:00
# it spreads the refreshes out over a time period
2015-05-04 12:21:00 +10:00
MessageBus . publish '/global/asset-version' , 'clobber'
2014-02-20 21:52:11 -08:00
end
2013-02-18 17:39:54 +11:00
def self . git_version
2013-02-25 19:42:20 +03:00
return $git_version if $git_version
2013-08-02 23:25:57 +02:00
# load the version stamped by the "build:stamp" task
f = Rails . root . to_s + " /config/version "
2013-02-18 18:00:49 +11:00
require f if File . exists? ( " #{ f } .rb " )
2013-02-18 17:39:54 +11:00
begin
2013-02-18 18:00:49 +11:00
$git_version || = ` git rev-parse HEAD ` . strip
2013-02-18 17:39:54 +11:00
rescue
2015-05-22 11:18:58 +10:00
$git_version = Discourse :: VERSION :: STRING
2013-02-18 17:39:54 +11:00
end
end
2014-09-09 17:04:10 -04:00
def self . git_branch
return $git_branch if $git_branch
begin
$git_branch || = ` git rev-parse --abbrev-ref HEAD ` . strip
rescue
$git_branch = " unknown "
end
end
2013-09-06 17:28:37 +10:00
# Either returns the site_contact_username user or the first admin.
def self . site_contact_user
2014-05-06 14:41:59 +01:00
user = User . find_by ( username_lower : SiteSetting . site_contact_username . downcase ) if SiteSetting . site_contact_username . present?
2015-11-24 14:37:33 -05:00
user || = ( system_user || User . admins . real . order ( :id ) . first )
2013-05-31 08:41:29 +10:00
end
2013-02-05 14:16:51 -05:00
2015-05-07 01:00:13 +02:00
SYSTEM_USER_ID || = - 1
2014-06-25 10:45:20 +10:00
2013-09-06 17:28:37 +10:00
def self . system_user
2016-04-25 23:03:17 +02:00
@system_user || = User . find_by ( id : SYSTEM_USER_ID )
2013-09-06 17:28:37 +10:00
end
2013-07-31 23:26:34 +02:00
def self . store
if SiteSetting . enable_s3_uploads?
@s3_store_loaded || = require 'file_store/s3_store'
2013-11-05 19:04:47 +01:00
FileStore :: S3Store . new
2013-07-31 23:26:34 +02:00
else
@local_store_loaded || = require 'file_store/local_store'
2013-11-05 19:04:47 +01:00
FileStore :: LocalStore . new
2013-07-31 23:26:34 +02:00
end
end
2013-10-09 15:10:37 +11:00
def self . current_user_provider
@current_user_provider || Auth :: DefaultCurrentUserProvider
end
def self . current_user_provider = ( val )
@current_user_provider = val
end
2013-11-05 19:04:47 +01:00
def self . asset_host
Rails . configuration . action_controller . asset_host
end
2014-02-12 20:37:28 -08:00
def self . readonly_channel
2014-02-19 18:21:41 +01:00
" /site/read-only "
2013-02-05 14:16:51 -05:00
end
2014-02-12 20:37:28 -08:00
2014-03-28 13:48:14 +11:00
# all forking servers must call this
# after fork, otherwise Discourse will be
# in a bad state
def self . after_fork
2015-05-06 09:53:10 +10:00
# note: all this reconnecting may no longer be needed per https://github.com/redis/redis-rb/pull/414
2014-04-07 19:38:47 +02:00
current_db = RailsMultisite :: ConnectionManagement . current_db
RailsMultisite :: ConnectionManagement . establish_connection ( db : current_db )
2015-05-04 12:21:00 +10:00
MessageBus . after_fork
2014-03-28 13:48:14 +11:00
SiteSetting . after_fork
$redis . client . reconnect
Rails . cache . reconnect
2014-05-08 08:05:28 +10:00
Logster . store . redis . reconnect
2014-04-23 11:01:17 +10:00
# shuts down all connections in the pool
Sidekiq . redis_pool . shutdown { | c | nil }
# re-establish
Sidekiq . redis = sidekiq_redis_config
2014-08-11 17:51:55 +10:00
start_connection_reaper
2016-07-16 15:11:34 +10:00
# in case v8 was initialized we want to make sure it is nil
PrettyText . reset_context
2014-05-08 08:05:28 +10:00
nil
2014-04-23 11:01:17 +10:00
end
2015-02-17 09:58:23 +11:00
def self . start_connection_reaper
return if GlobalSetting . connection_reaper_age < 1 ||
GlobalSetting . connection_reaper_interval < 1
2014-08-11 17:51:55 +10:00
# this helps keep connection counts in check
Thread . new do
while true
2015-02-17 09:58:23 +11:00
begin
sleep GlobalSetting . connection_reaper_interval
2015-10-17 11:29:16 +11:00
reap_connections ( GlobalSetting . connection_reaper_age , GlobalSetting . connection_reaper_max_age )
2015-02-17 09:58:23 +11:00
rescue = > e
Discourse . handle_exception ( e , { message : " Error reaping connections " } )
2014-08-11 17:51:55 +10:00
end
end
end
end
2015-10-17 11:29:16 +11:00
def self . reap_connections ( idle , max_age )
2015-02-17 09:58:23 +11:00
pools = [ ]
ObjectSpace . each_object ( ActiveRecord :: ConnectionAdapters :: ConnectionPool ) { | pool | pools << pool }
pools . each do | pool |
2015-10-17 11:29:16 +11:00
pool . drain ( idle . seconds , max_age . seconds )
2015-02-17 09:58:23 +11:00
end
end
2014-04-23 11:01:17 +10:00
def self . sidekiq_redis_config
2015-06-25 16:51:48 +10:00
conf = GlobalSetting . redis_config . dup
conf [ :namespace ] = 'sidekiq'
conf
2014-03-28 13:48:14 +11:00
end
2014-07-29 10:40:02 -04:00
def self . static_doc_topic_ids
[ SiteSetting . tos_topic_id , SiteSetting . guidelines_topic_id , SiteSetting . privacy_topic_id ]
end
2013-02-05 14:16:51 -05:00
end