Module: Discourse
- Defined in:
- lib/version.rb,
lib/discourse.rb
Defined Under Namespace
Modules: VERSION
Classes: CSRF, Deprecation, ImageMagickMissing, InvalidAccess, InvalidMigration, InvalidParameters, InvalidVersionListError, NotFound, NotLoggedIn, ReadOnly, ScssError, SiteSettingMissing, TooManyMatches, Utils
Constant Summary
collapse
- MAX_METADATA_FILE_SIZE =
64.kilobytes
- LAST_POSTGRES_READONLY_KEY =
"postgres:last_readonly"
Class Method Summary
collapse
-
.activate_plugins! ⇒ Object
-
.after_fork ⇒ Object
all forking servers must call this after fork, otherwise Discourse will be in a bad state.
-
.allow_dev_populate? ⇒ Boolean
-
.anonymous_filters ⇒ Object
-
.anonymous_locale(request) ⇒ Object
-
.anonymous_top_menu_items ⇒ Object
-
.apply_asset_filters(plugins, type, request) ⇒ Object
-
.apply_cdn_headers(headers) ⇒ Object
-
.asset_filter_options(type, request) ⇒ Object
-
.asset_host ⇒ Object
-
.assets_digest ⇒ Object
-
.auth_providers ⇒ Object
-
.authenticators ⇒ Object
-
.avatar_sizes ⇒ Object
-
.base_path(default_value = "") ⇒ Object
-
.base_protocol ⇒ Object
-
.base_uri(default_value = "") ⇒ Object
-
.base_url ⇒ Object
-
.base_url_no_prefix ⇒ Object
(also: base_url_no_path)
-
.cache ⇒ Object
-
.capture_exceptions(message: "", env: nil) ⇒ Object
-
.catch_job_exceptions! ⇒ Object
-
.clear_all_theme_cache! ⇒ Object
warning: this method is very expensive and shouldn’t be called in places where performance matters.
-
.clear_postgres_readonly! ⇒ Object
-
.clear_readonly! ⇒ Object
-
.clear_redis_readonly! ⇒ Object
-
.clear_urls! ⇒ Object
-
.current_hostname ⇒ Object
Get the current base URL for the current site.
-
.current_hostname_with_port ⇒ Object
-
.current_user_provider ⇒ Object
-
.current_user_provider=(val) ⇒ Object
-
.deprecate(warning, drop_from: nil, since: nil, raise_error: false, output_in_test: false) ⇒ Object
-
.disable_pg_force_readonly_mode ⇒ Object
-
.disable_readonly_mode(key = READONLY_MODE_KEY) ⇒ Object
-
.enable_pg_force_readonly_mode ⇒ Object
-
.enable_readonly_mode(key = READONLY_MODE_KEY) ⇒ Object
-
.enabled_auth_providers ⇒ Object
-
.enabled_authenticators ⇒ Object
-
.filters ⇒ Object
-
.find_compatible_git_resource(path) ⇒ Object
Find a compatible resource from a git repo.
-
.find_compatible_resource(version_list, target_version = ::Discourse::VERSION::STRING) ⇒ Object
lookup an external resource (theme/plugin)‘s best compatible version compatible resource files are YAML, in the format: `discourse_version: plugin/theme git reference.` For example: 2.5.0.beta6: c4a6c17 2.5.0.beta4: d1d2d3f 2.5.0.beta2: bbffee 2.4.4.beta6: some-other-branch-ref 2.4.2.beta1: v1-tag.
-
.find_plugin_css_assets(args) ⇒ Object
-
.find_plugin_js_assets(args) ⇒ Object
-
.find_plugins(args) ⇒ Object
-
.full_version ⇒ Object
-
.git_branch ⇒ Object
-
.git_version ⇒ Object
-
.handle_job_exception(ex, context = {}, parent_logger = nil) ⇒ Object
-
.has_needed_version?(current, needed) ⇒ Boolean
-
.is_cdn_request?(env, request_method) ⇒ Boolean
-
.is_parallel_test? ⇒ Boolean
-
.job_exception_stats ⇒ Object
-
.keep_readonly_mode(key, ttl:) ⇒ Object
-
.last_commit_date ⇒ Object
-
.official_plugins ⇒ Object
-
.os_hostname ⇒ Object
hostname of the server, operating system level called os_hostname so we do no confuse it with current_hostname.
-
.pg_readonly_mode? ⇒ Boolean
-
.plugin_themes ⇒ Object
-
.plugins ⇒ Object
-
.plugins_by_name ⇒ Object
-
.postgres_last_read_only ⇒ Object
Shared between processes.
-
.postgres_recently_readonly? ⇒ Boolean
-
.preload_rails! ⇒ Object
this is used to preload as much stuff as possible prior to forking in turn this can conserve large amounts of memory on forking servers.
-
.privacy_policy_url ⇒ Object
-
.readonly_channel ⇒ Object
-
.readonly_mode?(keys = READONLY_KEYS) ⇒ Boolean
-
.received_postgres_readonly! ⇒ Object
-
.received_redis_readonly! ⇒ Object
-
.recently_readonly? ⇒ Boolean
-
.redis_last_read_only ⇒ Object
-
.request_refresh!(user_ids: nil) ⇒ Object
-
.reset_active_record_cache ⇒ Object
-
.reset_active_record_cache_if_needed(e) ⇒ Object
-
.reset_catch_job_exceptions! ⇒ Object
-
.reset_job_exception_stats! ⇒ Object
-
.route_for(uri) ⇒ Object
-
.running_in_rack? ⇒ Boolean
-
.sidekiq_redis_config ⇒ Object
-
.site_contact_user ⇒ Object
Either returns the site_contact_username user or the first admin.
-
.skip_post_deployment_migrations? ⇒ Boolean
-
.staff_writes_only_mode? ⇒ Boolean
-
.static_doc_topic_ids ⇒ Object
-
.stats ⇒ Object
-
.store ⇒ Object
-
.system_user ⇒ Object
-
.top_menu_items ⇒ Object
-
.tos_url ⇒ Object
-
.try_git(git_cmd, default_value) ⇒ Object
-
.unofficial_plugins ⇒ Object
-
.urls_cache ⇒ Object
-
.visible_plugins ⇒ Object
-
.warn(message, env = nil) ⇒ Object
you can use Discourse.warn when you want to report custom environment with the error, this helps with grouping.
-
.warn_exception(e, message: "", env: nil) ⇒ Object
report a warning maintaining backtrack for logster.
Class Method Details
.activate_plugins! ⇒ Object
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
|
# File 'lib/discourse.rb', line 339
def self.activate_plugins!
@plugins = []
@plugins_by_name = {}
Plugin::Instance
.find_all("#{Rails.root}/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
@plugins_by_name[p.name] = p
dir_name = p.path.split("/")[-2]
if p.name != dir_name
STDERR.puts "Plugin name is '#{p.name}', but plugin directory is named '#{dir_name}'"
@plugins_by_name[dir_name] = p
end
else
STDERR.puts "Could not activate #{p.metadata.name}, discourse does not meet required version (#{v})"
end
end
DiscourseEvent.trigger(:after_plugin_activation)
end
|
.after_fork ⇒ Object
all forking servers must call this after fork, otherwise Discourse will be in a bad state
.allow_dev_populate? ⇒ Boolean
1183
1184
1185
|
# File 'lib/discourse.rb', line 1183
def self.allow_dev_populate?
Rails.env.development? || ENV["ALLOW_DEV_POPULATE"] == "1"
end
|
.anonymous_filters ⇒ Object
319
320
321
|
# File 'lib/discourse.rb', line 319
def self.anonymous_filters
@anonymous_filters ||= %i[latest top categories]
end
|
.anonymous_locale(request) ⇒ Object
327
328
329
|
# File 'lib/discourse.rb', line 327
def self.
@anonymous_top_menu_items ||= Discourse.anonymous_filters + %i[categories top]
end
|
.apply_asset_filters(plugins, type, request) ⇒ Object
402
403
404
405
|
# File 'lib/discourse.rb', line 402
def self.apply_asset_filters(plugins, type, request)
filter_opts = asset_filter_options(type, request)
plugins.select { |plugin| plugin.asset_filters.all? { |b| b.call(type, request, filter_opts) } }
end
|
1177
1178
1179
1180
1181
|
# File 'lib/discourse.rb', line 1177
def self.()
["Access-Control-Allow-Origin"] = "*"
["Access-Control-Allow-Methods"] = CDN_REQUEST_METHODS.join(", ")
end
|
.asset_filter_options(type, request) ⇒ Object
407
408
409
410
411
412
413
414
415
|
# File 'lib/discourse.rb', line 407
def self.asset_filter_options(type, request)
result = {}
return result if request.blank?
path = request.fullpath
result[:path] = path if path.present?
result
end
|
.asset_host ⇒ Object
892
893
894
|
# File 'lib/discourse.rb', line 892
def self.asset_host
Rails.configuration.action_controller.asset_host
end
|
.assets_digest ⇒ Object
459
460
461
462
463
464
465
466
467
468
469
470
|
# File 'lib/discourse.rb', line 459
def self.assets_digest
@assets_digest ||=
begin
digest = Digest::MD5.hexdigest(ActionView::Base.assets_manifest.assets.values.sort.join)
channel = "/global/asset-version"
message = MessageBus.last_message(channel)
MessageBus.publish channel, digest unless message && message.data == digest
digest
end
end
|
.auth_providers ⇒ Object
489
490
491
|
# File 'lib/discourse.rb', line 489
def self.auth_providers
BUILTIN_AUTH + DiscoursePluginRegistry.auth_providers.to_a
end
|
.authenticators ⇒ Object
497
498
499
500
501
|
# File 'lib/discourse.rb', line 497
def self.authenticators
auth_providers.map(&:authenticator)
end
|
.avatar_sizes ⇒ Object
334
335
336
337
|
# File 'lib/discourse.rb', line 334
def self.avatar_sizes
Set.new(SiteSetting.avatar_sizes.split("|").map(&:to_i))
end
|
.base_path(default_value = "") ⇒ Object
541
542
543
|
# File 'lib/discourse.rb', line 541
def self.base_path(default_value = "")
ActionController::Base.config.relative_url_root.presence || default_value
end
|
.base_protocol ⇒ Object
550
551
552
|
# File 'lib/discourse.rb', line 550
def self.base_protocol
SiteSetting.force_https? ? "https" : "http"
end
|
.base_uri(default_value = "") ⇒ Object
545
546
547
548
|
# File 'lib/discourse.rb', line 545
def self.base_uri(default_value = "")
deprecate("Discourse.base_uri is deprecated, use Discourse.base_path instead")
base_path(default_value)
end
|
.base_url ⇒ Object
570
571
572
|
# File 'lib/discourse.rb', line 570
def self.base_url
base_url_no_prefix + base_path
end
|
.base_url_no_prefix ⇒ Object
Also known as:
base_url_no_path
566
567
568
|
# File 'lib/discourse.rb', line 566
def self.base_url_no_prefix
"#{base_protocol}://#{current_hostname_with_port}"
end
|
.capture_exceptions(message: "", env: nil) ⇒ Object
998
999
1000
1001
1002
1003
|
# File 'lib/discourse.rb', line 998
def self.capture_exceptions(message: "", env: nil)
yield
rescue Exception => e
Discourse.warn_exception(e, message: message, env: env)
nil
end
|
.catch_job_exceptions! ⇒ Object
196
197
198
199
|
# File 'lib/discourse.rb', line 196
def self.catch_job_exceptions!
raise "tests only" if !Rails.env.test?
@catch_job_exceptions = true
end
|
.clear_all_theme_cache! ⇒ Object
warning: this method is very expensive and shouldn’t be called in places where performance matters. it’s meant to be called manually (e.g. in the rails console) when dealing with an emergency that requires invalidating theme cache
.clear_postgres_readonly! ⇒ Object
782
783
784
785
|
# File 'lib/discourse.rb', line 782
def self.clear_postgres_readonly!
redis.del(LAST_POSTGRES_READONLY_KEY)
postgres_last_read_only.clear(after_commit: false)
end
|
.clear_readonly! ⇒ Object
795
796
797
798
799
800
|
# File 'lib/discourse.rb', line 795
def self.clear_readonly!
clear_redis_readonly!
clear_postgres_readonly!
Site.clear_anon_cache!
true
end
|
.clear_redis_readonly! ⇒ Object
791
792
793
|
# File 'lib/discourse.rb', line 791
def self.clear_redis_readonly!
redis_last_read_only[Discourse.redis.namespace] = nil
end
|
.clear_urls! ⇒ Object
637
638
639
|
# File 'lib/discourse.rb', line 637
def self.clear_urls!
urls_cache.clear
end
|
.current_hostname ⇒ Object
Get the current base URL for the current site
.current_hostname_with_port ⇒ Object
554
555
556
557
558
559
560
561
562
563
564
|
# File 'lib/discourse.rb', line 554
def self.current_hostname_with_port
default_port = SiteSetting.force_https? ? 443 : 80
result = +"#{current_hostname}"
if SiteSetting.port.to_i > 0 && SiteSetting.port.to_i != default_port
result << ":#{SiteSetting.port}"
end
result << ":#{ENV["UNICORN_PORT"] || 3000}" if Rails.env.development? && SiteSetting.port.blank?
result
end
|
.current_user_provider ⇒ Object
.current_user_provider=(val) ⇒ Object
888
889
890
|
# File 'lib/discourse.rb', line 888
def self.current_user_provider=(val)
@current_user_provider = val
end
|
.deprecate(warning, drop_from: nil, since: nil, raise_error: false, output_in_test: false) ⇒ Object
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
|
# File 'lib/discourse.rb', line 1005
def self.deprecate(warning, drop_from: nil, since: nil, raise_error: false, output_in_test: false)
location = caller_locations[1].yield_self { |l| "#{l.path}:#{l.lineno}:in \`#{l.label}\`" }
warning = ["Deprecation notice:", warning]
warning << "(deprecated since Discourse #{since})" if since
warning << "(removal in Discourse #{drop_from})" if drop_from
warning << "\nAt #{location}"
warning = warning.join(" ")
raise Deprecation.new(warning) if raise_error
STDERR.puts(warning) if Rails.env.development?
STDERR.puts(warning) if output_in_test && Rails.env.test?
digest = Digest::MD5.hexdigest(warning)
redis_key = "deprecate-notice-#{digest}"
if !Rails.env.development? && Rails.logger && !GlobalSetting.skip_redis? &&
!Discourse.redis.without_namespace.get(redis_key)
Rails.logger.warn(warning)
begin
Discourse.redis.without_namespace.setex(redis_key, 3600, "x")
rescue Redis::CommandError => e
raise unless e.message =~ /READONLY/
end
end
warning
end
|
.disable_pg_force_readonly_mode ⇒ Object
731
732
733
734
735
736
737
|
# File 'lib/discourse.rb', line 731
def self.disable_pg_force_readonly_mode
RailsMultisite::ConnectionManagement.each_connection do
disable_readonly_mode(PG_FORCE_READONLY_MODE_KEY)
end
true
end
|
.disable_readonly_mode(key = READONLY_MODE_KEY) ⇒ Object
713
714
715
716
717
718
719
720
721
|
# File 'lib/discourse.rb', line 713
def self.disable_readonly_mode(key = READONLY_MODE_KEY)
if key == PG_READONLY_MODE_KEY || key == PG_FORCE_READONLY_MODE_KEY
Sidekiq.unpause! if Sidekiq.paused?
end
Discourse.redis.del(key)
MessageBus.publish(readonly_channel, false)
true
end
|
.enable_pg_force_readonly_mode ⇒ Object
723
724
725
726
727
728
729
|
# File 'lib/discourse.rb', line 723
def self.enable_pg_force_readonly_mode
RailsMultisite::ConnectionManagement.each_connection do
enable_readonly_mode(PG_FORCE_READONLY_MODE_KEY)
end
true
end
|
.enable_readonly_mode(key = READONLY_MODE_KEY) ⇒ Object
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
|
# File 'lib/discourse.rb', line 660
def self.enable_readonly_mode(key = READONLY_MODE_KEY)
if key == PG_READONLY_MODE_KEY || key == PG_FORCE_READONLY_MODE_KEY
Sidekiq.pause!("pg_failover") if !Sidekiq.paused?
end
if [USER_READONLY_MODE_KEY, PG_FORCE_READONLY_MODE_KEY, STAFF_WRITES_ONLY_MODE_KEY].include?(
key,
)
Discourse.redis.set(key, 1)
else
ttl =
case key
when PG_READONLY_MODE_KEY
PG_READONLY_MODE_KEY_TTL
else
READONLY_MODE_KEY_TTL
end
Discourse.redis.setex(key, ttl, 1)
keep_readonly_mode(key, ttl: ttl) if !Rails.env.test?
end
MessageBus.publish(readonly_channel, true)
true
end
|
.enabled_auth_providers ⇒ Object
493
494
495
|
# File 'lib/discourse.rb', line 493
def self.enabled_auth_providers
auth_providers.select { |provider| provider.authenticator.enabled? }
end
|
.enabled_authenticators ⇒ Object
503
504
505
|
# File 'lib/discourse.rb', line 503
def self.enabled_authenticators
authenticators.select { |authenticator| authenticator.enabled? }
end
|
.filters ⇒ Object
315
316
317
|
# File 'lib/discourse.rb', line 315
def self.filters
@filters ||= %i[latest unread new unseen top read posted bookmarks]
end
|
.find_compatible_git_resource(path) ⇒ Object
Find a compatible resource from a git repo
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
# File 'lib/version.rb', line 93
def self.find_compatible_git_resource(path)
return unless File.directory?("#{path}/.git")
tree_info =
Discourse::Utils.execute_command(
"git",
"-C",
path,
"ls-tree",
"-l",
"HEAD",
Discourse::VERSION_COMPATIBILITY_FILENAME,
)
blob_size = tree_info.split[3].to_i
if blob_size > Discourse::MAX_METADATA_FILE_SIZE
$stderr.puts "#{Discourse::VERSION_COMPATIBILITY_FILENAME} file in #{path} too big"
return
end
compat_resource =
Discourse::Utils.execute_command(
"git",
"-C",
path,
"show",
"HEAD@{upstream}:#{Discourse::VERSION_COMPATIBILITY_FILENAME}",
)
Discourse.find_compatible_resource(compat_resource)
rescue InvalidVersionListError => e
$stderr.puts "Invalid version list in #{path}"
rescue Discourse::Utils::CommandError => e
nil
end
|
.find_compatible_resource(version_list, target_version = ::Discourse::VERSION::STRING) ⇒ Object
lookup an external resource (theme/plugin)‘s best compatible version compatible resource files are YAML, in the format: `discourse_version: plugin/theme git reference.` For example:
2.5.0.beta6: c4a6c17
2.5.0.beta4: d1d2d3f
2.5.0.beta2: bbffee
2.4.4.beta6: some-other-branch-ref
2.4.2.beta1: v1-tag
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
# File 'lib/version.rb', line 38
def self.find_compatible_resource(version_list, target_version = ::Discourse::VERSION::STRING)
return unless version_list.present?
begin
version_list = YAML.safe_load(version_list)
rescue Psych::SyntaxError, Psych::DisallowedClass => e
end
raise InvalidVersionListError unless version_list.is_a?(Hash)
version_list =
version_list
.transform_keys do |v|
Gem::Requirement.parse(v)
rescue Gem::Requirement::BadRequirementError => e
raise InvalidVersionListError, "Invalid version specifier: #{v}"
end
.sort_by do |parsed_requirement, _|
operator, version = parsed_requirement
[version, operator == "<" ? 0 : 1]
end
parsed_target_version = Gem::Version.new(target_version)
lowest_matching_entry =
version_list.find do |parsed_requirement, target|
req_operator, req_version = parsed_requirement
req_operator = "<=" if req_operator == "="
if !%w[<= <].include?(req_operator)
raise InvalidVersionListError,
"Invalid version specifier operator for '#{req_operator} #{req_version}'. Operator must be one of <= or <"
end
resolved_requirement = Gem::Requirement.new("#{req_operator} #{req_version.to_s}")
resolved_requirement.satisfied_by?(parsed_target_version)
end
return if lowest_matching_entry.nil?
checkout_version = lowest_matching_entry[1]
begin
Discourse::Utils.execute_command "git",
"check-ref-format",
"--allow-onelevel",
checkout_version
rescue RuntimeError
raise InvalidVersionListError, "Invalid ref name: #{checkout_version}"
end
checkout_version
end
|
.find_plugin_css_assets(args) ⇒ Object
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
|
# File 'lib/discourse.rb', line 417
def self.find_plugin_css_assets(args)
plugins = apply_asset_filters(self.find_plugins(args), :css, args[:request])
assets = []
targets = [nil]
targets << :mobile if args[:mobile_view]
targets << :desktop if args[:desktop_view]
targets.each do |target|
assets +=
plugins
.find_all { |plugin| plugin.css_asset_exists?(target) }
.map do |plugin|
target.nil? ? plugin.directory_name : "#{plugin.directory_name}_#{target}"
end
end
assets.map! { |asset| "#{asset}_rtl" } if args[:rtl]
assets
end
|
.find_plugin_js_assets(args) ⇒ Object
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
|
# File 'lib/discourse.rb', line 439
def self.find_plugin_js_assets(args)
plugins =
self
.find_plugins(args)
.select do |plugin|
plugin.js_asset_exists? || plugin. || plugin.admin_js_asset_exists?
end
plugins = apply_asset_filters(plugins, :js, args[:request])
plugins.flat_map do |plugin|
assets = []
assets << "plugins/#{plugin.directory_name}" if plugin.js_asset_exists?
assets << "plugins/#{plugin.directory_name}_extra" if plugin.
assets << "plugins/#{plugin.directory_name}_admin" if plugin.admin_js_asset_exists?
assets
end
end
|
.find_plugins(args) ⇒ Object
392
393
394
395
396
397
398
399
400
|
# File 'lib/discourse.rb', line 392
def self.find_plugins(args)
plugins.select do |plugin|
next if args[:include_official] == false && plugin.metadata.official?
next if args[:include_unofficial] == false && !plugin.metadata.official?
next if !args[:include_disabled] && !plugin.enabled?
true
end
end
|
.full_version ⇒ Object
828
829
830
831
832
833
834
|
# File 'lib/discourse.rb', line 828
def self.full_version
@full_version ||=
begin
git_cmd = 'git describe --dirty --match "v[0-9]*" 2> /dev/null'
self.try_git(git_cmd, "unknown")
end
end
|
.git_branch ⇒ Object
822
823
824
825
826
|
# File 'lib/discourse.rb', line 822
def self.git_branch
@git_branch ||=
self.try_git("git branch --show-current", nil) ||
self.try_git("git config user.discourse-version", "unknown")
end
|
.git_version ⇒ Object
814
815
816
817
818
819
820
|
# File 'lib/discourse.rb', line 814
def self.git_version
@git_version ||=
begin
git_cmd = "git rev-parse HEAD"
self.try_git(git_cmd, Discourse::VERSION::STRING)
end
end
|
.handle_job_exception(ex, context = {}, parent_logger = nil) ⇒ Object
Log an exception.
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. See app/jobs/base.rb for the error_context function.
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
|
# File 'lib/discourse.rb', line 213
def self.handle_job_exception(ex, context = {}, parent_logger = nil)
return if ex.class == Jobs::HandledExceptionWrapper
context ||= {}
parent_logger ||= Sidekiq
job = context[:job]
if Hash === job
job_class = job["class"]
job_exception_stats[job_class] += 1 if job_class
end
job_exception_stats[job] += 1 if job.class == Class && ::Jobs::Base > job
cm = RailsMultisite::ConnectionManagement
parent_logger.handle_exception(
ex,
{ current_db: cm.current_db, current_hostname: cm.current_hostname }.merge(context),
)
raise ex if Rails.env.test? && !@catch_job_exceptions
end
|
.has_needed_version?(current, needed) ⇒ Boolean
26
27
28
|
# File 'lib/version.rb', line 26
def self.has_needed_version?(current, needed)
Gem::Version.new(current) >= Gem::Version.new(needed)
end
|
.is_cdn_request?(env, request_method) ⇒ Boolean
1167
1168
1169
1170
1171
1172
1173
1174
1175
|
# File 'lib/discourse.rb', line 1167
def self.is_cdn_request?(env, request_method)
return unless CDN_REQUEST_METHODS.include?(request_method)
cdn_hostnames = GlobalSetting.cdn_hostnames
return if cdn_hostnames.blank?
requested_hostname = env[REQUESTED_HOSTNAME] || env[Rack::HTTP_HOST]
cdn_hostnames.include?(requested_hostname)
end
|
.is_parallel_test? ⇒ Boolean
1161
1162
1163
|
# File 'lib/discourse.rb', line 1161
def self.is_parallel_test?
ENV["RAILS_ENV"] == "test" && ENV["TEST_ENV_NUMBER"]
end
|
.job_exception_stats ⇒ Object
185
186
187
|
# File 'lib/discourse.rb', line 185
def self.job_exception_stats
@job_exception_stats
end
|
.keep_readonly_mode(key, ttl:) ⇒ Object
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
|
# File 'lib/discourse.rb', line 686
def self.keep_readonly_mode(key, ttl:)
@mutex ||= Mutex.new
@mutex.synchronize do
@dbs ||= Set.new
@dbs << RailsMultisite::ConnectionManagement.current_db
@threads ||= {}
unless @threads[key]&.alive?
@threads[key] = Thread.new do
while @dbs.size > 0
sleep ttl / 2
@mutex.synchronize do
@dbs.each do |db|
RailsMultisite::ConnectionManagement.with_connection(db) do
@dbs.delete(db) if !Discourse.redis.expire(key, ttl)
end
end
end
end
end
end
end
end
|
.last_commit_date ⇒ Object
836
837
838
839
840
841
842
843
|
# File 'lib/discourse.rb', line 836
def self.last_commit_date
@last_commit_date ||=
begin
git_cmd = 'git log -1 --format="%ct"'
seconds = self.try_git(git_cmd, nil)
seconds.nil? ? nil : DateTime.strptime(seconds, "%s")
end
end
|
.official_plugins ⇒ Object
384
385
386
|
# File 'lib/discourse.rb', line 384
def self.official_plugins
plugins.find_all { |p| p.metadata.official? }
end
|
.os_hostname ⇒ Object
hostname of the server, operating system level called os_hostname so we do no confuse it with current_hostname
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
|
# File 'lib/discourse.rb', line 520
def self.os_hostname
@os_hostname ||=
begin
require "socket"
Socket.gethostname
rescue => e
warn_exception(e, message: "Socket.gethostname is not working")
begin
`hostname`.strip
rescue => e
warn_exception(e, message: "hostname command is not working")
"unknown_host"
end
end
end
|
.pg_readonly_mode? ⇒ Boolean
747
748
749
|
# File 'lib/discourse.rb', line 747
def self.pg_readonly_mode?
Discourse.redis.get(PG_READONLY_MODE_KEY).present?
end
|
.plugin_themes ⇒ Object
380
381
382
|
# File 'lib/discourse.rb', line 380
def self.plugin_themes
@plugin_themes ||= plugins.map(&:themes).flatten
end
|
.plugins ⇒ Object
368
369
370
|
# File 'lib/discourse.rb', line 368
def self.plugins
@plugins ||= []
end
|
.plugins_by_name ⇒ Object
372
373
374
|
# File 'lib/discourse.rb', line 372
def self.plugins_by_name
@plugins_by_name ||= {}
end
|
.postgres_last_read_only ⇒ Object
752
753
754
|
# File 'lib/discourse.rb', line 752
def self.postgres_last_read_only
@postgres_last_read_only ||= DistributedCache.new("postgres_last_read_only")
end
|
.postgres_recently_readonly? ⇒ Boolean
761
762
763
764
765
766
|
# File 'lib/discourse.rb', line 761
def self.postgres_recently_readonly?
seconds =
postgres_last_read_only.defer_get_set("timestamp") { redis.get(LAST_POSTGRES_READONLY_KEY) }
seconds ? Time.zone.at(seconds.to_i) > 15.seconds.ago : false
end
|
.preload_rails! ⇒ Object
this is used to preload as much stuff as possible prior to forking in turn this can conserve large amounts of memory on forking servers
.privacy_policy_url ⇒ Object
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
|
# File 'lib/discourse.rb', line 621
def self.privacy_policy_url
if SiteSetting.privacy_policy_url.present?
SiteSetting.privacy_policy_url
else
urls_cache["privacy_policy"] ||= (
if SiteSetting.privacy_topic_id > 0 && Topic.exists?(id: SiteSetting.privacy_topic_id)
"#{Discourse.base_path}/privacy"
else
:nil
end
)
urls_cache["privacy_policy"] != :nil ? urls_cache["privacy_policy"] : nil
end
end
|
.readonly_channel ⇒ Object
896
897
898
|
# File 'lib/discourse.rb', line 896
def self.readonly_channel
"/site/read-only"
end
|
.readonly_mode?(keys = READONLY_KEYS) ⇒ Boolean
739
740
741
|
# File 'lib/discourse.rb', line 739
def self.readonly_mode?(keys = READONLY_KEYS)
recently_readonly? || GlobalSetting.pg_force_readonly_mode || Discourse.redis.exists?(*keys)
end
|
.received_postgres_readonly! ⇒ Object
774
775
776
777
778
779
780
|
# File 'lib/discourse.rb', line 774
def self.received_postgres_readonly!
time = Time.zone.now
redis.set(LAST_POSTGRES_READONLY_KEY, time.to_i.to_s)
postgres_last_read_only.clear(after_commit: false)
time
end
|
.received_redis_readonly! ⇒ Object
787
788
789
|
# File 'lib/discourse.rb', line 787
def self.received_redis_readonly!
redis_last_read_only[Discourse.redis.namespace] = Time.zone.now
end
|
.recently_readonly? ⇒ Boolean
768
769
770
771
772
|
# File 'lib/discourse.rb', line 768
def self.recently_readonly?
redis_read_only = redis_last_read_only[Discourse.redis.namespace]
(redis_read_only.present? && redis_read_only > 15.seconds.ago) || postgres_recently_readonly?
end
|
.redis_last_read_only ⇒ Object
757
758
759
|
# File 'lib/discourse.rb', line 757
def self.redis_last_read_only
@redis_last_read_only ||= {}
end
|
.request_refresh!(user_ids: nil) ⇒ Object
802
803
804
805
806
807
808
809
810
811
812
|
# File 'lib/discourse.rb', line 802
def self.request_refresh!(user_ids: nil)
if user_ids
MessageBus.publish("/refresh_client", "clobber", user_ids: user_ids)
else
MessageBus.publish("/global/asset-version", "clobber")
end
end
|
.reset_active_record_cache ⇒ Object
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
|
# File 'lib/discourse.rb', line 1058
def self.reset_active_record_cache
ActiveRecord::Base.connection.query_cache.clear
(ActiveRecord::Base.connection.tables - %w[schema_migrations versions]).each do |table|
begin
table.classify.constantize.reset_column_information
rescue StandardError
nil
end
end
nil
end
|
.reset_active_record_cache_if_needed(e) ⇒ Object
1048
1049
1050
1051
1052
1053
1054
1055
1056
|
# File 'lib/discourse.rb', line 1048
def self.reset_active_record_cache_if_needed(e)
last_cache_reset = Discourse.last_ar_cache_reset
if e && e.message =~ /UndefinedColumn/ &&
(last_cache_reset.nil? || last_cache_reset < 30.seconds.ago)
Rails.logger.warn "Clearing Active Record cache, this can happen if schema changed while site is running or in a multisite various databases are running different schemas. Consider running rake multisite:migrate."
Discourse.last_ar_cache_reset = Time.zone.now
Discourse.reset_active_record_cache
end
end
|
.reset_catch_job_exceptions! ⇒ Object
201
202
203
204
|
# File 'lib/discourse.rb', line 201
def self.reset_catch_job_exceptions!
raise "tests only" if !Rails.env.test?
remove_instance_variable(:@catch_job_exceptions)
end
|
.reset_job_exception_stats! ⇒ Object
189
190
191
|
# File 'lib/discourse.rb', line 189
def self.reset_job_exception_stats!
@job_exception_stats = Hash.new(0)
end
|
.route_for(uri) ⇒ Object
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
|
# File 'lib/discourse.rb', line 574
def self.route_for(uri)
unless uri.is_a?(URI)
uri =
begin
URI(uri)
rescue ArgumentError, URI::Error
end
end
return unless uri
path = +(uri.path || "")
if !uri.host ||
(uri.host == Discourse.current_hostname && path.start_with?(Discourse.base_path))
path.slice!(Discourse.base_path)
return Rails.application.routes.recognize_path(path)
end
nil
rescue ActionController::RoutingError
nil
end
|
.running_in_rack? ⇒ Boolean
1070
1071
1072
|
# File 'lib/discourse.rb', line 1070
def self.running_in_rack?
ENV["DISCOURSE_RUNNING_IN_RACK"] == "1"
end
|
.sidekiq_redis_config ⇒ Object
1036
1037
1038
1039
1040
|
# File 'lib/discourse.rb', line 1036
def self.sidekiq_redis_config
conf = GlobalSetting.redis_config.dup
conf[:namespace] = SIDEKIQ_NAMESPACE
conf
end
|
Either returns the site_contact_username user or the first admin.
854
855
856
857
858
859
860
|
# File 'lib/discourse.rb', line 854
def self.site_contact_user
user =
User.find_by(
username_lower: SiteSetting.site_contact_username.downcase,
) if SiteSetting.site_contact_username.present?
user ||= (system_user || User.admins.real.order(:id).first)
end
|
.skip_post_deployment_migrations? ⇒ Boolean
1074
1075
1076
|
# File 'lib/discourse.rb', line 1074
def self.skip_post_deployment_migrations?
%w[1 true].include?(ENV["SKIP_POST_DEPLOYMENT_MIGRATIONS"]&.to_s)
end
|
.staff_writes_only_mode? ⇒ Boolean
743
744
745
|
# File 'lib/discourse.rb', line 743
def self.staff_writes_only_mode?
Discourse.redis.get(STAFF_WRITES_ONLY_MODE_KEY).present?
end
|
.static_doc_topic_ids ⇒ Object
1042
1043
1044
|
# File 'lib/discourse.rb', line 1042
def self.static_doc_topic_ids
[SiteSetting.tos_topic_id, SiteSetting.guidelines_topic_id, SiteSetting.privacy_topic_id]
end
|
.stats ⇒ Object
880
881
882
|
# File 'lib/discourse.rb', line 880
def self.stats
PluginStore.new("stats")
end
|
.store ⇒ Object
870
871
872
873
874
875
876
877
878
|
# File 'lib/discourse.rb', line 870
def self.store
if SiteSetting.Upload.enable_s3_uploads
@s3_store_loaded ||= require "file_store/s3_store"
FileStore::S3Store.new
else
@local_store_loaded ||= require "file_store/local_store"
FileStore::LocalStore.new
end
end
|
.system_user ⇒ Object
864
865
866
867
868
|
# File 'lib/discourse.rb', line 864
def self.system_user
@system_users ||= {}
current_db = RailsMultisite::ConnectionManagement.current_db
@system_users[current_db] ||= User.find_by(id: SYSTEM_USER_ID)
end
|
323
324
325
|
# File 'lib/discourse.rb', line 323
def self.
@top_menu_items ||= Discourse.filters + [:categories]
end
|
.tos_url ⇒ Object
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
|
# File 'lib/discourse.rb', line 605
def self.tos_url
if SiteSetting.tos_url.present?
SiteSetting.tos_url
else
urls_cache["tos"] ||= (
if SiteSetting.tos_topic_id > 0 && Topic.exists?(id: SiteSetting.tos_topic_id)
"#{Discourse.base_path}/tos"
else
:nil
end
)
urls_cache["tos"] != :nil ? urls_cache["tos"] : nil
end
end
|
.try_git(git_cmd, default_value) ⇒ Object
845
846
847
848
849
850
851
|
# File 'lib/discourse.rb', line 845
def self.try_git(git_cmd, default_value)
begin
`#{git_cmd}`.strip
rescue StandardError
default_value
end.presence || default_value
end
|
.unofficial_plugins ⇒ Object
388
389
390
|
# File 'lib/discourse.rb', line 388
def self.unofficial_plugins
plugins.find_all { |p| !p.metadata.official? }
end
|
.urls_cache ⇒ Object
601
602
603
|
# File 'lib/discourse.rb', line 601
def self.urls_cache
@urls_cache ||= DistributedCache.new("urls_cache")
end
|
.visible_plugins ⇒ Object
376
377
378
|
# File 'lib/discourse.rb', line 376
def self.visible_plugins
plugins.filter(&:visible?)
end
|
.warn(message, env = nil) ⇒ Object
you can use Discourse.warn when you want to report custom environment with the error, this helps with grouping
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
|
# File 'lib/discourse.rb', line 937
def self.warn(message, env = nil)
append = env ? (+" ") << env.map { |k, v| "#{k}: #{v}" }.join(" ") : ""
if !(Logster::Logger === Rails.logger)
Rails.logger.warn("#{message}#{append}")
return
end
loggers = [Rails.logger]
loggers.concat(Rails.logger.chained) if Rails.logger.chained
logster_env = env
if old_env = Thread.current[Logster::Logger::LOGSTER_ENV]
logster_env = Logster::Message.populate_from_env(old_env)
env.each { |k, v| logster_env[k] = v }
end
loggers.each do |logger|
if !(Logster::Logger === logger)
logger.warn("#{message} #{append}")
next
end
logger.store.report(::Logger::Severity::WARN, "discourse", message, env: logster_env)
end
if old_env
env.each do |k, v|
logster_env.delete(k)
end
end
nil
end
|
.warn_exception(e, message: "", env: nil) ⇒ Object
report a warning maintaining backtrack for logster
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
|
# File 'lib/discourse.rb', line 977
def self.warn_exception(e, message: "", env: nil)
if Rails.logger.respond_to? :add_with_opts
env ||= {}
env[:current_db] ||= RailsMultisite::ConnectionManagement.current_db
Rails.logger.add_with_opts(
::Logger::Severity::WARN,
"#{message} : #{e.class.name} : #{e}",
"discourse-exception",
backtrace: e.backtrace.join("\n"),
env: env,
)
else
Rails.logger.warn("#{message} #{e}\n#{e.backtrace.join("\n")}")
end
rescue StandardError
STDERR.puts "Failed to report exception #{e} #{message}"
end
|