Class: Plugin::Instance

Inherits:
Object
  • Object
show all
Defined in:
lib/plugin/instance.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(metadata = nil, path = nil) ⇒ Instance

Returns a new instance of Instance.



100
101
102
103
104
# File 'lib/plugin/instance.rb', line 100

def initialize( = nil, path = nil)
  @metadata = 
  @path = path
  @idx = 0
end

Instance Attribute Details

#admin_routeObject (readonly)

Returns the value of attribute admin_route.



48
49
50
# File 'lib/plugin/instance.rb', line 48

def admin_route
  @admin_route
end

#metadataObject

Returns the value of attribute metadata.



47
48
49
# File 'lib/plugin/instance.rb', line 47

def 
  @metadata
end

#pathObject

Returns the value of attribute path.



47
48
49
# File 'lib/plugin/instance.rb', line 47

def path
  @path
end

Class Method Details

.find_all(parent_path) ⇒ Object



87
88
89
90
91
92
# File 'lib/plugin/instance.rb', line 87

def self.find_all(parent_path)
  [].tap do |plugins|
    # also follows symlinks - http://stackoverflow.com/q/357754
    Dir["#{parent_path}/*/plugin.rb"].sort.each { |path| plugins << parse_from_source(path) }
  end
end

.parse_from_source(path) ⇒ Object



94
95
96
97
98
# File 'lib/plugin/instance.rb', line 94

def self.parse_from_source(path)
  source = File.read(path)
   = Plugin::Metadata.parse(source)
  self.new(, path)
end

.statsObject

This method returns Core stats + stats registered by plugins



83
84
85
# File 'lib/plugin/instance.rb', line 83

def self.stats
  Stat.all_stats
end

Instance Method Details

#activate!Object

note, we need to be able to parse separately to activation. this allows us to present information about a plugin in the UI prior to activations



779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
# File 'lib/plugin/instance.rb', line 779

def activate!
  self.instance_eval File.read(path), path
  if auto_assets = generate_automatic_assets!
    assets.concat(auto_assets)
  end

  register_assets! if assets.present?
  register_locales!
  register_service_workers!

  seed_data.each { |key, value| DiscoursePluginRegistry.register_seed_data(key, value) }

  # Allow plugins to `register_asset` for images under /assets
  Rails.configuration.assets.paths << File.dirname(path) + "/assets"

  # Automatically include rake tasks
  Rake.add_rakelib(File.dirname(path) + "/lib/tasks")

  # Automatically include migrations
  migration_paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths
  migration_paths << File.dirname(path) + "/db/migrate"

  unless Discourse.skip_post_deployment_migrations?
    migration_paths << "#{File.dirname(path)}/#{Discourse::DB_POST_MIGRATE_PATH}"
  end

  public_data = File.dirname(path) + "/public"
  if Dir.exist?(public_data)
    target = Rails.root.to_s + "/public/plugins/"

    Discourse::Utils.execute_command("mkdir", "-p", target)
    target << name.gsub(/\s/, "_")

    Discourse::Utils.atomic_ln_s(public_data, target)
  end

  write_extra_js!
end

#add_admin_route(label, location, opts = {}) ⇒ Object



113
114
115
116
117
118
119
# File 'lib/plugin/instance.rb', line 113

def add_admin_route(label, location, opts = {})
  @admin_route = {
    label: label,
    location: location,
    use_new_show_route: opts.fetch(:use_new_show_route, false),
  }
end

#add_api_key_scope(resource, action) ⇒ Object

Register a new API key scope.

Example: add_api_key_scope(:groups, { delete: { actions: %w, params: %i } })

This scope lets you add members to a group. Additionally, you can specify which group ids are allowed. The delete action is added to the groups resource.



931
932
933
# File 'lib/plugin/instance.rb', line 931

def add_api_key_scope(resource, action)
  DiscoursePluginRegistry.register_api_key_scope_mapping({ resource => action }, self)
end

#add_api_parameter_route(methods: nil, actions: nil, formats: nil) ⇒ Object

Register a route which can be authenticated using an api key or user api key in a query parameter rather than a header. For example:

add_api_parameter_route(

methods: :get,
actions: "users#bookmarks",
formats: :ics

)

See Auth::DefaultCurrentUserProvider::PARAMETER_API_PATTERNS for more examples and Auth::DefaultCurrentUserProvider#api_parameter_allowed? for implementation



975
976
977
978
979
980
# File 'lib/plugin/instance.rb', line 975

def add_api_parameter_route(methods: nil, actions: nil, formats: nil)
  DiscoursePluginRegistry.register_api_parameter_route(
    RouteMatcher.new(methods: methods, actions: actions, formats: formats),
    self,
  )
end

#add_body_class(class_name) ⇒ Object

Applies to all sites in a multisite environment. Ignores plugin.enabled?



366
367
368
# File 'lib/plugin/instance.rb', line 366

def add_body_class(class_name)
  reloadable_patch { |plugin| ::ApplicationHelper.extra_body_classes << class_name }
end

#add_class_method(klass_name, attr, &block) ⇒ Object

Adds a class method to a class, respecting if plugin is enabled



394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
# File 'lib/plugin/instance.rb', line 394

def add_class_method(klass_name, attr, &block)
  reloadable_patch do |plugin|
    klass =
      begin
        klass_name.to_s.classify.constantize
      rescue StandardError
        klass_name.to_s.constantize
      end

    hidden_method_name = :"#{attr}_without_enable_check"
    klass.public_send(:define_singleton_method, hidden_method_name, &block)

    klass.public_send(:define_singleton_method, attr) do |*args, **kwargs|
      public_send(hidden_method_name, *args, **kwargs) if plugin.enabled?
    end
  end
end

#add_custom_reviewable_filter(filter) ⇒ Object

Receives an array with two elements:

  1. A symbol that represents the name of the value to filter.

  2. A Proc that takes the existing ActiveRecord::Relation and the value received from the front-end.



920
921
922
# File 'lib/plugin/instance.rb', line 920

def add_custom_reviewable_filter(filter)
  reloadable_patch { Reviewable.add_custom_filter(filter) }
end

#add_directory_column(column_name, query:, icon: nil) ⇒ Object



523
524
525
526
527
528
529
530
531
532
533
# File 'lib/plugin/instance.rb', line 523

def add_directory_column(column_name, query:, icon: nil)
  validate_directory_column_name(column_name)

  DiscourseEvent.on("before_directory_refresh") do
    DirectoryColumn.find_or_create_plugin_directory_column(
      column_name: column_name,
      icon: icon,
      query: query,
    )
  end
end

#add_filter_custom_filter(name, &block) ⇒ Object

Allows to define custom filter utilizing the user’s input. Ensure proper input sanitization before using it in a query.

Example usage:

add_filter_custom_filter("word_count") do |scope, value|
  scope.where(word_count: value)
end


261
262
263
# File 'lib/plugin/instance.rb', line 261

def add_filter_custom_filter(name, &block)
  DiscoursePluginRegistry.register_custom_filter_mapping({ name => block }, self)
end

#add_model_callback(klass_name, callback, options = {}, &block) ⇒ Object



412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
# File 'lib/plugin/instance.rb', line 412

def add_model_callback(klass_name, callback, options = {}, &block)
  reloadable_patch do |plugin|
    klass =
      begin
        klass_name.to_s.classify.constantize
      rescue StandardError
        klass_name.to_s.constantize
      end

    # generate a unique method name
    method_name = "#{plugin.name}_#{klass.name}_#{callback}#{@idx}".underscore
    @idx += 1
    hidden_method_name = :"#{method_name}_without_enable_check"
    klass.public_send(:define_method, hidden_method_name, &block)

    klass.public_send(callback, **options) do |*args, **kwargs|
      public_send(hidden_method_name, *args, **kwargs) if plugin.enabled?
    end

    hidden_method_name
  end
end

#add_permitted_post_create_param(name, type = :string) ⇒ Object

Add a permitted_create_param to Post, respecting if the plugin is enabled



464
465
466
467
468
# File 'lib/plugin/instance.rb', line 464

def add_permitted_post_create_param(name, type = :string)
  reloadable_patch do |plugin|
    ::Post.plugin_permitted_create_params[name] = { plugin: plugin, type: type }
  end
end

#add_permitted_post_update_param(attribute, &block) ⇒ Object

Add a permitted_update_param to Post, respecting if the plugin is enabled



471
472
473
474
475
# File 'lib/plugin/instance.rb', line 471

def add_permitted_post_update_param(attribute, &block)
  reloadable_patch do |plugin|
    ::Post.plugin_permitted_update_params[attribute] = { plugin: plugin, handler: block }
  end
end

#add_permitted_reviewable_param(type, param) ⇒ Object



991
992
993
# File 'lib/plugin/instance.rb', line 991

def add_permitted_reviewable_param(type, param)
  DiscoursePluginRegistry.register_reviewable_param({ type: type, param: param }, self)
end

#add_post_revision_notifier_recipients(&block) ⇒ Object

Allows to add additional user_ids to the list of people notified when doing a post revision



445
446
447
448
449
450
451
# File 'lib/plugin/instance.rb', line 445

def add_post_revision_notifier_recipients(&block)
  reloadable_patch do |plugin|
    ::PostActionNotifier.add_post_revision_notifier_recipients do |post_revision|
      plugin.enabled? ? block.call(post_revision) : []
    end
  end
end

#add_preloaded_group_custom_field(field) ⇒ Object

Applies to all sites in a multisite environment. Ignores plugin.enabled?



454
455
456
# File 'lib/plugin/instance.rb', line 454

def add_preloaded_group_custom_field(field)
  reloadable_patch { |plugin| ::Group.preloaded_custom_field_names << field }
end

#add_preloaded_topic_list_custom_field(field) ⇒ Object

Applies to all sites in a multisite environment. Ignores plugin.enabled?



459
460
461
# File 'lib/plugin/instance.rb', line 459

def add_preloaded_topic_list_custom_field(field)
  reloadable_patch { |plugin| ::TopicList.preloaded_custom_fields << field }
end

#add_report(name, &block) ⇒ Object

Applies to all sites in a multisite environment. Ignores plugin.enabled?



206
207
208
# File 'lib/plugin/instance.rb', line 206

def add_report(name, &block)
  reloadable_patch { |plugin| Report.add_report(name, &block) }
end

#add_request_rate_limiter(identifier:, key:, activate_when:, global: false, after: nil, before: nil) ⇒ Object

This is an experimental API and may be changed or removed in the future without deprecation.

Adds a custom rate limiter to the request rate limiters stack. Only one rate limiter is used per request and the first rate limiter in the stack that is active is used. By default the rate limiters stack contains the following rate limiters:

`RequestTracker::RateLimiters::User` - Rate limits authenticated requests based on the user's id
`RequestTracker::RateLimiters::IP` - Rate limits requests based on the IP address

Examples:

Adding a rate limiter that rate limits all requests based on the country of the IP address


add_request_rate_limiter(
  identifier: :country,
  key: ->(request) { "country/#{DiscourseIpInfo.get(request.ip)[:country]}" },
  activate_when: ->(request) { DiscourseIpInfo.get(request.ip)[:country].present? },
)

Parameters:

  • identifier (Symbol)

    A unique identifier for the rate limiter.

  • key (Proc)

    A lambda/proc that defines the ‘rate_limit_key`.

    • Receives ‘request` (An instance of `Rack::Request`) as argument.

    • Should return a string representing the rate limit key.

  • activate_when (Proc)

    A lambda/proc that defines when the rate limiter should be used for a request.

    • Receives ‘request` (An instance of `Rack::Request`) as argument.

    • Should return ‘true` if the rate limiter is active, otherwise `false`.

  • global (Boolean) (defaults to: false)

    Whether the rate limiter applies globally across all sites. Defaults to ‘false`.

    • Ignored if ‘klass` is provided.

  • after (Class, nil) (defaults to: nil)

    The rate limiter class after which the new rate limiter should be added.

  • before (Class, nil) (defaults to: nil)

    The rate limiter class before which the new rate limiter should be added.

Raises:

  • (ArgumentError)


1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
# File 'lib/plugin/instance.rb', line 1293

def add_request_rate_limiter(
  identifier:,
  key:,
  activate_when:,
  global: false,
  after: nil,
  before: nil
)
  raise ArgumentError, "only one of `after` or `before` can be provided" if after && before

  stack = Middleware::RequestTracker.rate_limiters_stack

  if (reference_klass = after || before) && !stack.include?(reference_klass)
    raise ArgumentError, "#{reference_klass} is not a valid value. Must be one of #{stack}"
  end

  klass =
    Class.new(RequestTracker::RateLimiters::Base) do
      define_method(:rate_limit_key) { key.call(@request) }
      define_method(:rate_limit_globally?) { global }
      define_method(:active?) { activate_when.call(@request) }
      define_method(:error_code_identifier) { identifier }
    end

  if after
    stack.insert_after(after, klass)
  elsif before
    stack.insert_before(before, klass)
  else
    stack.prepend(klass)
  end
end

Register a ReviewableScore setting_name associated with a reason. We’ll use this to build a site setting link and add it to the reason’s translation.

If your plugin has a reason translation looking like this:

my_plugin_reason: "This is the reason this post was flagged. See %{link}."

And you associate the reason with a setting:

add_reviewable_score_link(:my_plugin_reason, 'a_plugin_setting')

We’ll generate the following link and attach it to the translation:

<a href="/admin/site_settings/category/all_results?filter=a_plugin_setting">
  a plugin setting
</a>


1029
1030
1031
1032
1033
1034
# File 'lib/plugin/instance.rb', line 1029

def add_reviewable_score_link(reason, setting_name)
  DiscoursePluginRegistry.register_reviewable_score_link(
    { reason: reason.to_sym, setting: setting_name },
    self,
  )
end

#add_to_class(class_name, attr, &block) ⇒ Object

Extend a class but check that the plugin is enabled for class methods use ‘add_class_method`



376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
# File 'lib/plugin/instance.rb', line 376

def add_to_class(class_name, attr, &block)
  reloadable_patch do |plugin|
    klass =
      begin
        class_name.to_s.classify.constantize
      rescue StandardError
        class_name.to_s.constantize
      end
    hidden_method_name = :"#{attr}_without_enable_check"
    klass.public_send(:define_method, hidden_method_name, &block)

    klass.public_send(:define_method, attr) do |*args, **kwargs|
      public_send(hidden_method_name, *args, **kwargs) if plugin.enabled?
    end
  end
end

#add_to_serializer(serializer, attr, deprecated_respect_plugin_enabled = nil, respect_plugin_enabled: true, include_condition: nil, &block) ⇒ Object



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/plugin/instance.rb', line 152

def add_to_serializer(
  serializer,
  attr,
  deprecated_respect_plugin_enabled = nil,
  respect_plugin_enabled: true,
  include_condition: nil,
  &block
)
  if !deprecated_respect_plugin_enabled.nil?
    Discourse.deprecate(
      "add_to_serializer's respect_plugin_enabled argument should be passed as a keyword argument",
    )
    respect_plugin_enabled = deprecated_respect_plugin_enabled
  end

  if attr.to_s.starts_with?("include_")
    Discourse.deprecate(
      "add_to_serializer should not be used to directly override include_*? methods. Use the include_condition keyword argument instead",
    )
  end

  reloadable_patch do |plugin|
    base =
      begin
        "#{serializer.to_s.classify}Serializer".constantize
      rescue StandardError
        "#{serializer}Serializer".constantize
      end

    # we have to work through descendants cause serializers may already be baked and cached
    ([base] + base.descendants).each do |klass|
      unless attr.to_s.start_with?("include_")
        klass.attributes(attr)

        if respect_plugin_enabled || include_condition
          # Don't include serialized methods if the plugin is disabled
          klass.public_send(:define_method, "include_#{attr}?") do
            next false if respect_plugin_enabled && !plugin.enabled?
            next instance_exec(&include_condition) if include_condition
            true
          end
        end
      end

      klass.public_send(:define_method, attr, &block)
    end
  end
end

#add_topic_static_page(page, options = {}, &blk) ⇒ Object

Allows customizing existing topic-backed static pages, like: faq, tos, privacy (see: StaticController) The block passed to this method has to return a SiteSetting name that contains a topic id.

add_topic_static_page("faq") do |controller|
  current_user&.locale == "pl" ? "polish_faq_topic_id" : "faq_topic_id"
end

You can also add new pages in a plugin, but remember to add a route, for example:

get "contact" => "static#show", id: "contact"


1068
1069
1070
# File 'lib/plugin/instance.rb', line 1068

def add_topic_static_page(page, options = {}, &blk)
  StaticController::CUSTOM_PAGES[page] = blk ? { topic_id: blk } : options
end

#add_user_api_key_scope(scope_name, matcher_parameters) ⇒ Object

Register a new UserApiKey scope, and its allowed routes. Scope will be prefixed with the (parameterized) plugin name followed by a colon.

For example, if discourse-awesome-plugin registered this:

add_user_api_key_scope(:read_my_route,

methods: :get,
actions: "mycontroller#myaction",
formats: :ics,
params: :testparam

)

The scope registered would be ‘discourse-awesome-plugin:read_my_route`

Multiple matchers can be attached by supplying an array of parameter hashes

See UserApiKeyScope::SCOPES for more examples And lib/route_matcher.rb for the route matching logic

Raises:

  • (ArgumentError)


953
954
955
956
957
958
959
960
961
962
# File 'lib/plugin/instance.rb', line 953

def add_user_api_key_scope(scope_name, matcher_parameters)
  raise ArgumentError.new("scope_name must be a symbol") if !scope_name.is_a?(Symbol)
  matcher_parameters = [matcher_parameters] if !matcher_parameters.is_a?(Array)

  prefixed_scope_name = :"#{(name || directory_name).parameterize}:#{scope_name}"
  DiscoursePluginRegistry.register_user_api_key_scope_mapping(
    { prefixed_scope_name => matcher_parameters&.map { |m| RouteMatcher.new(**m) } },
    self,
  )
end

#admin_js_asset_exists?Boolean

Returns:

  • (Boolean)


912
913
914
915
# File 'lib/plugin/instance.rb', line 912

def admin_js_asset_exists?
  # If this directory exists, ember-cli will output a .js file
  File.exist?("#{File.dirname(@path)}/admin/assets/javascripts")
end

#after_initialize(&block) ⇒ Object



560
561
562
# File 'lib/plugin/instance.rb', line 560

def after_initialize(&block)
  initializers << block
end

#allow_public_user_custom_field(field) ⇒ Object



230
231
232
# File 'lib/plugin/instance.rb', line 230

def allow_public_user_custom_field(field)
  DiscoursePluginRegistry.register_public_user_custom_field(field, self)
end

#allow_staff_user_custom_field(field) ⇒ Object



226
227
228
# File 'lib/plugin/instance.rb', line 226

def allow_staff_user_custom_field(field)
  DiscoursePluginRegistry.register_staff_user_custom_field(field, self)
end

#auth_provider(opts) ⇒ Object



818
819
820
821
822
823
824
825
826
827
828
# File 'lib/plugin/instance.rb', line 818

def auth_provider(opts)
  after_initialize do
    provider = Auth::AuthProvider.new

    Auth::AuthProvider.auth_attributes.each do |sym|
      provider.public_send("#{sym}=", opts.delete(sym)) if opts.has_key?(sym)
    end

    DiscoursePluginRegistry.register_auth_provider(provider)
  end
end

#auto_generated_pathObject



556
557
558
# File 'lib/plugin/instance.rb', line 556

def auto_generated_path
  File.dirname(path) << "/auto_generated"
end

#automatic_assetsObject



759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
# File 'lib/plugin/instance.rb', line 759

def automatic_assets
  css = styles.join("\n")
  js = javascripts.join("\n")

  # Generate an IIFE for the JS
  js = "(function(){#{js}})();" if js.present?

  result = []
  result << [css, "css"] if css.present?
  result << [js, "js"] if js.present?

  result.map do |asset, extension|
    hash = Digest::SHA1.hexdigest asset
    ["#{auto_generated_path}/plugin_#{hash}.#{extension}", asset]
  end
end

#commit_hashObject



564
565
566
# File 'lib/plugin/instance.rb', line 564

def commit_hash
  git_repo.latest_local_commit
end

#commit_urlObject



568
569
570
571
# File 'lib/plugin/instance.rb', line 568

def commit_url
  return if commit_hash.blank?
  "#{git_repo.url}/commit/#{commit_hash}"
end

#configurable?Boolean

Returns:

  • (Boolean)


133
134
135
# File 'lib/plugin/instance.rb', line 133

def configurable?
  true
end

#css_asset_exists?(target = nil) ⇒ Boolean

Returns:

  • (Boolean)


899
900
901
# File 'lib/plugin/instance.rb', line 899

def css_asset_exists?(target = nil)
  DiscoursePluginRegistry.stylesheets_exists?(directory_name, target)
end

#custom_avatar_column(column) ⇒ Object



358
359
360
361
362
363
# File 'lib/plugin/instance.rb', line 358

def custom_avatar_column(column)
  reloadable_patch do |plugin|
    UserLookup.lookup_columns << column
    UserLookup.lookup_columns.uniq!
  end
end

#delete_extra_automatic_assets(good_paths) ⇒ Object



535
536
537
538
539
540
541
542
543
544
545
# File 'lib/plugin/instance.rb', line 535

def delete_extra_automatic_assets(good_paths)
  return unless Dir.exist? auto_generated_path

  filenames = good_paths.map { |f| File.basename(f) }
  # nuke old files
  Dir.foreach(auto_generated_path) do |p|
    next if %w[. ..].include?(p)
    next if filenames.include?(p)
    File.delete(auto_generated_path + "/#{p}")
  end
end

#directoryObject



552
553
554
# File 'lib/plugin/instance.rb', line 552

def directory
  File.dirname(path)
end

#directory_nameObject



895
896
897
# File 'lib/plugin/instance.rb', line 895

def directory_name
  @directory_name ||= File.dirname(path).split("/").last
end

#discourse_owned?Boolean

Returns:

  • (Boolean)


577
578
579
580
581
582
583
584
# File 'lib/plugin/instance.rb', line 577

def discourse_owned?
  return false if commit_hash.blank?
  parsed_commit_url = UrlHelper.relaxed_parse(self.commit_url)
  return false if parsed_commit_url.blank?
  github_org = parsed_commit_url.path.split("/")[1]
  (github_org == "discourse" || github_org == "discourse-org") &&
    parsed_commit_url.host == "github.com"
end

#enabled?Boolean

Returns:

  • (Boolean)


141
142
143
144
# File 'lib/plugin/instance.rb', line 141

def enabled?
  return false if !configurable?
  @enabled_site_setting ? SiteSetting.get(@enabled_site_setting) : true
end

#enabled_site_setting(setting = nil) ⇒ Object



844
845
846
847
848
849
850
# File 'lib/plugin/instance.rb', line 844

def enabled_site_setting(setting = nil)
  if setting
    @enabled_site_setting = setting
  else
    @enabled_site_setting
  end
end

#ensure_directory(path) ⇒ Object



547
548
549
550
# File 'lib/plugin/instance.rb', line 547

def ensure_directory(path)
  dirname = File.dirname(path)
  FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
end

#extend_content_security_policy(extension) ⇒ Object



681
682
683
# File 'lib/plugin/instance.rb', line 681

def extend_content_security_policy(extension)
  csp_extensions << extension
end

#extend_list_method(klass, method, new_attributes) ⇒ Object



875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
# File 'lib/plugin/instance.rb', line 875

def extend_list_method(klass, method, new_attributes)
  register_name = [klass, method].join("_").underscore
  DiscoursePluginRegistry.define_filtered_register(register_name)
  DiscoursePluginRegistry.public_send(
    "register_#{register_name.singularize}",
    new_attributes,
    self,
  )

  original_method_alias = "__original_#{method}__"
  return if klass.respond_to?(original_method_alias)
  reloadable_patch do
    klass.singleton_class.alias_method(original_method_alias, method)
    klass.define_singleton_method(method) do
      public_send(original_method_alias) |
        DiscoursePluginRegistry.public_send(register_name).flatten
    end
  end
end

#extra_js_asset_exists?Boolean

Returns:

  • (Boolean)


908
909
910
# File 'lib/plugin/instance.rb', line 908

def extra_js_asset_exists?
  File.exist?(extra_js_file_path)
end

#full_admin_routeObject



121
122
123
124
125
126
127
128
129
130
131
# File 'lib/plugin/instance.rb', line 121

def full_admin_route
  route = self.admin_route
  return unless route

  route
    .slice(:location, :label, :use_new_show_route)
    .tap do |admin_route|
      path = admin_route[:use_new_show_route] ? "show" : admin_route[:location]
      admin_route[:full_location] = "adminPlugins.#{path}"
    end
end

#gem(name, version, opts = {}) ⇒ Object

shotgun approach to gem loading, in future we need to hack bundler

to at least determine dependencies do not clash before loading

Additionally we want to support multiple ruby versions correctly and so on

This is a very rough initial implementation



836
837
838
# File 'lib/plugin/instance.rb', line 836

def gem(name, version, opts = {})
  PluginGem.load(path, name, version, opts)
end

#generate_automatic_assets!Object

will make sure all the assets this plugin needs are registered



508
509
510
511
512
513
514
515
516
517
518
519
520
521
# File 'lib/plugin/instance.rb', line 508

def generate_automatic_assets!
  paths = []
  assets = []

  automatic_assets.each do |path, contents|
    write_asset(path, contents)
    paths << path
    assets << [path, nil, directory_name]
  end

  delete_extra_automatic_assets(paths)

  assets
end

#git_repoObject



573
574
575
# File 'lib/plugin/instance.rb', line 573

def git_repo
  @git_repo ||= GitRepo.new(directory, name)
end

#hide_pluginObject



840
841
842
# File 'lib/plugin/instance.rb', line 840

def hide_plugin
  @hidden = true
end

#humanized_nameObject



148
149
150
# File 'lib/plugin/instance.rb', line 148

def humanized_name
  (setting_category_name || name).delete_prefix("Discourse ").delete_prefix("discourse-")
end

#javascript_includesObject



860
861
862
863
864
865
866
867
868
# File 'lib/plugin/instance.rb', line 860

def javascript_includes
  assets
    .map do |asset, opts|
      next if opts == :vendored_core_pretty_text
      next unless asset =~ DiscoursePluginRegistry::JS_REGEX
      asset
    end
    .compact
end

#js_asset_exists?Boolean

Returns:

  • (Boolean)


903
904
905
906
# File 'lib/plugin/instance.rb', line 903

def js_asset_exists?
  # If assets/javascripts exists, ember-cli will output a .js file
  File.exist?("#{File.dirname(@path)}/assets/javascripts")
end

#listen_for(event_name) ⇒ Object



664
665
666
667
# File 'lib/plugin/instance.rb', line 664

def listen_for(event_name)
  return unless self.respond_to?(event_name)
  DiscourseEvent.on(event_name, &self.method(event_name))
end

#notify_after_initializeObject



607
608
609
610
611
612
613
614
615
616
617
618
# File 'lib/plugin/instance.rb', line 607

def notify_after_initialize
  initializers.each do |callback|
    begin
      callback.call(self)
    rescue ActiveRecord::StatementInvalid => e
      # When running `db:migrate` for the first time on a new database,
      # plugin initializers might try to use models.
      # Tolerate it.
      raise e unless e.message.try(:include?, "PG::UndefinedTable")
    end
  end
end

#on(event_name, &block) ⇒ Object

A proxy to ‘DiscourseEvent.on` which does nothing if the plugin is disabled



587
588
589
# File 'lib/plugin/instance.rb', line 587

def on(event_name, &block)
  DiscourseEvent.on(event_name) { |*args, **kwargs| block.call(*args, **kwargs) if enabled? }
end

#on_enabled_change(&block) ⇒ Object

A proxy to ‘DiscourseEvent.on(:site_setting_changed)` triggered when the plugin enabled setting specified by `enabled_site_setting` value is changed, including when the plugin is turned off.

It is useful when the plugin needs to perform tasks like properly clearing caches when enabled/disabled note it will not be triggered when a plugin is installed/uninstalled by adding/removing its code



596
597
598
599
600
601
602
603
604
605
# File 'lib/plugin/instance.rb', line 596

def on_enabled_change(&block)
  event_proc =
    Proc.new do |setting_name, old_value, new_value|
      block.call(old_value, new_value) if setting_name == @enabled_site_setting
    end
  DiscourseEvent.on(:site_setting_changed, &event_proc)

  # returns the block to be used for DiscourseEvent.off(:site_setting_changed, &block) for testing purposes
  event_proc
end

#register_anonymous_cache_key(key, &block) ⇒ Object



106
107
108
109
110
111
# File 'lib/plugin/instance.rb', line 106

def register_anonymous_cache_key(key, &block)
  key_method = "key_#{key}"
  add_to_class(Middleware::AnonymousCache::Helper, key_method, &block)
  Middleware::AnonymousCache.cache_key_segments[key] = key_method
  Middleware::AnonymousCache.compile_key_builder
end

#register_asset(file, opts = nil) ⇒ Object



717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
# File 'lib/plugin/instance.rb', line 717

def register_asset(file, opts = nil)
  raise <<~ERROR if file.end_with?(".hbs", ".handlebars")
      [#{name}] Handlebars templates can no longer be included via `register_asset`.
      Any hbs files under `assets/javascripts` will be automatically compiled and included."
    ERROR

  raise <<~ERROR if file.start_with?("javascripts/") && file.end_with?(".js", ".js.es6")
      [#{name}] Javascript files under `assets/javascripts` are automatically included in JS bundles.
      Manual register_asset calls should be removed. (attempted to add #{file})
    ERROR

  if opts && opts == :vendored_core_pretty_text
    full_path = DiscoursePluginRegistry.core_asset_for_name(file)
  else
    full_path = File.dirname(path) << "/assets/" << file
  end

  assets << [full_path, opts, directory_name]
end

#register_asset_filter(&blk) ⇒ Object

Register a block to run when adding css and js assets Two arguments will be passed: (type, request) Type is :css or :js. ‘request` is an instance of Rack::Request When using this, make sure to consider the effect on AnonymousCache



689
690
691
# File 'lib/plugin/instance.rb', line 689

def register_asset_filter(&blk)
  asset_filters << blk
end

#register_bookmarkable(klass) ⇒ Object

Register a class that implements [BaseBookmarkable], which represents another

ActiveRecord::Model

that may be bookmarked via the [Bookmark] model’s

polymorphic association. The class handles create and destroy hooks, querying, and reminders among other things.



1221
1222
1223
1224
# File 'lib/plugin/instance.rb', line 1221

def register_bookmarkable(klass)
  return if Bookmark.registered_bookmarkable_from_type(klass.model.name).present?
  DiscoursePluginRegistry.register_bookmarkable(RegisteredBookmarkable.new(klass), self)
end

#register_category_custom_field_type(name, type, max_length: nil) ⇒ Object

Applies to all sites in a multisite environment. Ignores plugin.enabled?



621
622
623
624
625
# File 'lib/plugin/instance.rb', line 621

def register_category_custom_field_type(name, type, max_length: nil)
  reloadable_patch do |plugin|
    Category.register_custom_field_type(name, type, max_length: max_length)
  end
end

#register_css(style) ⇒ Object



669
670
671
# File 'lib/plugin/instance.rb', line 669

def register_css(style)
  styles << style
end

#register_custom_filter_by_status(status, &block) ⇒ Object

Allows to define custom “status:” filter. Example usage:

register_custom_filter_by_status("foobar") do |scope|
  scope.where("word_count = 42")
end


269
270
271
# File 'lib/plugin/instance.rb', line 269

def register_custom_filter_by_status(status, &block)
  TopicsFilter.add_filter_by_status(status, &block)
end

#register_custom_html(hash) ⇒ Object



701
702
703
# File 'lib/plugin/instance.rb', line 701

def register_custom_html(hash)
  DiscoursePluginRegistry.custom_html.merge!(hash)
end

#register_demon_process(demon_class) ⇒ Object

Register a new demon process to be forked by the Unicorn master. The demon_class should inherit from Demon::Base. With great power comes great responsibility - this method should be used with extreme caution. See ‘config/unicorn.conf.rb`.



986
987
988
989
# File 'lib/plugin/instance.rb', line 986

def register_demon_process(demon_class)
  raise "Not a demon class" if !demon_class.ancestors.include?(Demon::Base)
  DiscoursePluginRegistry.demon_processes << demon_class
end

#register_editable_group_custom_field(field) ⇒ Object



250
251
252
# File 'lib/plugin/instance.rb', line 250

def register_editable_group_custom_field(field)
  DiscoursePluginRegistry.register_editable_group_custom_field(field, self)
end

#register_editable_topic_custom_field(field, staff_only: false) ⇒ Object



234
235
236
237
238
239
240
# File 'lib/plugin/instance.rb', line 234

def register_editable_topic_custom_field(field, staff_only: false)
  if staff_only
    DiscoursePluginRegistry.register_staff_editable_topic_custom_field(field, self)
  else
    DiscoursePluginRegistry.register_public_editable_topic_custom_field(field, self)
  end
end

#register_editable_user_custom_field(field, staff_only: false) ⇒ Object



242
243
244
245
246
247
248
# File 'lib/plugin/instance.rb', line 242

def register_editable_user_custom_field(field, staff_only: false)
  if staff_only
    DiscoursePluginRegistry.register_staff_editable_user_custom_field(field, self)
  else
    DiscoursePluginRegistry.register_self_editable_user_custom_field(field, self)
  end
end

#register_email_notification_filter(&block) ⇒ Object

Registers a new email notification filter. Notification is passed into block, and if all filters return ‘true`, the email notification will be sent.



1003
1004
1005
# File 'lib/plugin/instance.rb', line 1003

def register_email_notification_filter(&block)
  DiscoursePluginRegistry.register_email_notification_filter(block, self)
end

#register_email_poller(poller) ⇒ Object



712
713
714
715
# File 'lib/plugin/instance.rb', line 712

def register_email_poller(poller)
  plugin = self
  DiscoursePluginRegistry.register_mail_poller(poller) if plugin.enabled?
end

#register_email_unsubscriber(type, unsubscriber) ⇒ Object

Let plugin define custom unsubscribe keys, set custom instance variables on the ‘EmailController#unsubscribe` action, and describe what unsubscribing for that key does.

The method receives a class that inherits from ‘Email::BaseEmailUnsubscriber`. Take a look at it to know how to implement your child class.

In conjunction with this, you’ll have to:

- Register a new connector under app/views/connectors/unsubscribe_options.
We'll include the HTML inside the unsubscribe form, so you can add your fields using the
instance variables you set in the controller previously. When the form is submitted,
it sends the updated preferences to `EmailController#perform_unsubscribe`.

- Your code is responsible for creating the custom key by calling `UnsubscribeKey#create_key_for`.

Raises:

  • (ArgumentError)


1087
1088
1089
1090
1091
1092
1093
1094
1095
# File 'lib/plugin/instance.rb', line 1087

def register_email_unsubscriber(type, unsubscriber)
  core_types = [UnsubscribeKey::ALL_TYPE, UnsubscribeKey::DIGEST_TYPE, UnsubscribeKey::TOPIC_TYPE]
  raise ArgumentError.new("Type already exists") if core_types.include?(type)
  if !unsubscriber.ancestors.include?(EmailControllerHelper::BaseEmailUnsubscriber)
    raise ArgumentError.new("Not an email unsubscriber")
  end

  DiscoursePluginRegistry.register_email_unsubscriber({ type => unsubscriber }, self)
end

#register_emoji(name, url, group = Emoji::DEFAULT_GROUP) ⇒ Object



749
750
751
752
753
# File 'lib/plugin/instance.rb', line 749

def register_emoji(name, url, group = Emoji::DEFAULT_GROUP)
  name = Emoji.sanitize_emoji_name(name)
  Plugin::CustomEmoji.register(name, url, group)
  Emoji.clear_cache
end

#register_group_custom_field_type(name, type, max_length: nil) ⇒ Object

Applies to all sites in a multisite environment. Ignores plugin.enabled?



642
643
644
645
646
# File 'lib/plugin/instance.rb', line 642

def register_group_custom_field_type(name, type, max_length: nil)
  reloadable_patch do |plugin|
    ::Group.register_custom_field_type(name, type, max_length: max_length)
  end
end

#register_group_param(param) ⇒ Object

Add a permitted_param to Group, respecting if the plugin is enabled Used in GroupsController#update and Admin::GroupsController#create



479
480
481
# File 'lib/plugin/instance.rb', line 479

def register_group_param(param)
  DiscoursePluginRegistry.register_group_param(param, self)
end

#register_groups_callback_for_users_search_controller_action(callback, &block) ⇒ Object

Add a custom callback for search to Group Callback is called in UsersController#search_users Block takes groups and optional current_user For example: plugin.register_groups_callback_for_users_search_controller_action(:admins_filter) do |groups, user|

groups.where(name: "admins")

end



490
491
492
493
494
495
496
# File 'lib/plugin/instance.rb', line 490

def register_groups_callback_for_users_search_controller_action(callback, &block)
  if DiscoursePluginRegistry.groups_callback_for_users_search_controller_action.key?(callback)
    raise "groups_callback_for_users_search_controller_action callback already registered"
  end

  DiscoursePluginRegistry.groups_callback_for_users_search_controller_action[callback] = block
end

#register_hashtag_data_source(klass) ⇒ Object

Used to register data sources for HashtagAutocompleteService to look up results based on a #hashtag string.

signatures:

Roughly corresponding to a model, this is used as a unique
key for the datasource and is also used when allowing different
contexts to search for and lookup these types. The `category`
and `tag` types are registered by default.
def self.type
end

The FontAwesome icon to use for the data source in the search results
and cooked markdown.
def self.icon
end

@param {Guardian} guardian - Current user's guardian, used for permission-based filtering
@param {Array} slugs - An array of strings that represent slugs to search this type for,
                       e.g. category slugs.
@returns {Hash} A hash with the slug as the key and the URL of the record as the value.
def self.lookup(guardian, slugs)
end

@param {Guardian} guardian - Current user's guardian, used for permission-based filtering
@param {String} term - The search term used to filter results
@param {Integer} limit - The number of search results that should be returned by the query
@returns {Array} An Array of HashtagAutocompleteService::HashtagItem
def self.search(guardian, term, limit)
end

@param {Array} search_results - An array of HashtagAutocompleteService::HashtagItem to sort
@param {String} term - The search term which was used, which may help with sorting.
@returns {Array} An Array of HashtagAutocompleteService::HashtagItem
def self.search_sort(search_results, term)
end

@param {Guardian} guardian - Current user's guardian, used for permission-based filtering
@param {Integer} limit - The number of search results that should be returned by the query
@returns {Array} An Array of HashtagAutocompleteService::HashtagItem
def self.search_without_term(guardian, limit)
end

Parameters:

  • klass (Class)
    • Must be a class that implements methods with the following



1181
1182
1183
# File 'lib/plugin/instance.rb', line 1181

def register_hashtag_data_source(klass)
  DiscoursePluginRegistry.register_hashtag_autocomplete_data_source(klass, self)
end

#register_hashtag_type_priority_for_context(type, context, priority) ⇒ Object

Used to set up the priority ordering of hashtag autocomplete results by type using HashtagAutocompleteService.

Parameters:

  • type (String)
    • Roughly corresponding to a model, can only be registered once

    per context. The ‘category` and `tag` types are registered for the `topic-composer` context by default in that priority order.

  • context (String)
    • The context in which the hashtag lookup or search is happening

    in. For example, the Discourse composer context is ‘topic-composer`. Different contexts may want to have different priority orderings for certain types of hashtag result.

  • priority (Integer)
    • A number value for ordering type results when hashtag searches

    or lookups occur. Priority is ordered by DESCENDING order.



1198
1199
1200
1201
1202
1203
# File 'lib/plugin/instance.rb', line 1198

def register_hashtag_type_priority_for_context(type, context, priority)
  DiscoursePluginRegistry.register_hashtag_autocomplete_contextual_type_priority(
    { type: type, context: context, priority: priority },
    self,
  )
end

#register_html_builder(name, &block) ⇒ Object



705
706
707
708
709
710
# File 'lib/plugin/instance.rb', line 705

def register_html_builder(name, &block)
  plugin = self
  DiscoursePluginRegistry.register_html_builder(name) do |*args, **kwargs|
    block.call(*args, **kwargs) if plugin.enabled?
  end
end

#register_javascript(js) ⇒ Object



673
674
675
# File 'lib/plugin/instance.rb', line 673

def register_javascript(js)
  javascripts << js
end

#register_locale(locale, opts = {}) ⇒ Object

Parameters:

  • opts (Hash) (defaults to: {})

    a customizable set of options

Options Hash (opts):

  • :name (String)
  • :nativeName (String)
  • :fallbackLocale (String)
  • :plural (Hash)


697
698
699
# File 'lib/plugin/instance.rb', line 697

def register_locale(locale, opts = {})
  locales << [locale, opts]
end

#register_modifier(modifier_name, &blk) ⇒ Object



201
202
203
# File 'lib/plugin/instance.rb', line 201

def register_modifier(modifier_name, &blk)
  DiscoursePluginRegistry.register_modifier(self, modifier_name, &blk)
end

#register_notification_consolidation_plan(plan) ⇒ Object

If your plugin creates notifications, and you’d like to consolidate/collapse similar ones, you’re in the right place. This method receives a plan object, which must be an instance of ‘Notifications::ConsolidateNotifications`.

Instead of using ‘Notification#create!`, you should use `Notification#consolidate_or_save!`, which will automatically pick your plan and apply it, updating an already consolidated notification, consolidating multiple ones, or creating a regular one.

The rule object is quite complex. We strongly recommend you write tests to ensure your plugin consolidates notifications correctly.



1049
1050
1051
1052
1053
1054
# File 'lib/plugin/instance.rb', line 1049

def register_notification_consolidation_plan(plan)
  if !plan.class.ancestors.include?(Notifications::ConsolidationPlan)
    raise ArgumentError.new("Not a consolidation plan")
  end
  DiscoursePluginRegistry.register_notification_consolidation_plan(plan, self)
end

#register_post_action_notify_user_handler(handler) ⇒ Object

Register a block that will be called when PostActionCreator is going to notify a user of a post action. If any of these handlers returns false the default PostCreator call will be skipped.



1244
1245
1246
# File 'lib/plugin/instance.rb', line 1244

def register_post_action_notify_user_handler(handler)
  DiscoursePluginRegistry.register_post_action_notify_user_handler(handler, self)
end

#register_post_custom_field_type(name, type, max_length: nil) ⇒ Object

Applies to all sites in a multisite environment. Ignores plugin.enabled?



635
636
637
638
639
# File 'lib/plugin/instance.rb', line 635

def register_post_custom_field_type(name, type, max_length: nil)
  reloadable_patch do |plugin|
    ::Post.register_custom_field_type(name, type, max_length: max_length)
  end
end

#register_post_stripper(&block) ⇒ Object

We strip posts before detecting mentions, oneboxes, attachments etc. We strip those elements that shouldn’t be detected. For example, a mention inside a quote should be ignored, so we strip it off. Using this API plugins can register their own post strippers.



1252
1253
1254
# File 'lib/plugin/instance.rb', line 1252

def register_post_stripper(&block)
  DiscoursePluginRegistry.register_post_stripper({ block: block }, self)
end

#register_preloaded_category_custom_fields(field) ⇒ Object

Registers a category custom field to be loaded when rendering a category list Example usage:

register_preloaded_category_custom_fields("custom_field")


350
351
352
# File 'lib/plugin/instance.rb', line 350

def register_preloaded_category_custom_fields(field)
  Site.preloaded_category_custom_fields << field
end

#register_presence_channel_prefix(prefix, &block) ⇒ Object

Register a new PresenceChannel prefix. See PresenceChannel.register_prefix for usage instructions



997
998
999
# File 'lib/plugin/instance.rb', line 997

def register_presence_channel_prefix(prefix, &block)
  DiscoursePluginRegistry.register_presence_channel_prefix([prefix, block], self)
end

#register_problem_check(klass) ⇒ Object



354
355
356
# File 'lib/plugin/instance.rb', line 354

def register_problem_check(klass)
  DiscoursePluginRegistry.register_problem_check(klass, self)
end

#register_push_notification_filter(&block) ⇒ Object

Registers a new push notification filter. User and notification payload are passed into block, and if all filters return ‘true`, the push notification will be sent.



1009
1010
1011
# File 'lib/plugin/instance.rb', line 1009

def register_push_notification_filter(&block)
  DiscoursePluginRegistry.register_push_notification_filter(block, self)
end

#register_reviewable_type(reviewable_type_class) ⇒ Object



870
871
872
873
# File 'lib/plugin/instance.rb', line 870

def register_reviewable_type(reviewable_type_class)
  return unless reviewable_type_class < Reviewable
  extend_list_method(Reviewable, :types, reviewable_type_class)
end

#register_search_advanced_filter(trigger, &block) ⇒ Object

Allows to define custom search filters. Example usage:

Search.advanced_filter(/^min_chars:(\d+)$/) do |posts, match|
  posts.where("(SELECT LENGTH(p2.raw) FROM posts p2 WHERE p2.id = posts.id) >= ?", match.to_i)
end


285
286
287
# File 'lib/plugin/instance.rb', line 285

def register_search_advanced_filter(trigger, &block)
  Search.advanced_filter(trigger, &block)
end

#register_search_advanced_order(trigger, &block) ⇒ Object

Allows to define custom search order. Example usage:

Search.advanced_order(:chars) do |posts|
  posts.reorder("(SELECT LENGTH(raw) FROM posts WHERE posts.topic_id = subquery.topic_id) DESC")
end


277
278
279
# File 'lib/plugin/instance.rb', line 277

def register_search_advanced_order(trigger, &block)
  Search.advanced_order(trigger, &block)
end

#register_search_group_query_callback(callback) ⇒ Object



1256
1257
1258
# File 'lib/plugin/instance.rb', line 1256

def register_search_group_query_callback(callback)
  DiscoursePluginRegistry.register_search_groups_set_query_callback(callback, self)
end

#register_search_topic_eager_load(tables = nil, &block) ⇒ Object

Allow to eager load additional tables in Search. Useful to avoid N+1 performance problems. Example usage:

register_search_topic_eager_load do |opts|
  %i(example_table)
end

OR

register_search_topic_eager_load(%i(example_table))


314
315
316
# File 'lib/plugin/instance.rb', line 314

def register_search_topic_eager_load(tables = nil, &block)
  Search.custom_topic_eager_load(tables, &block)
end

#register_seed_data(key, value) ⇒ Object



741
742
743
# File 'lib/plugin/instance.rb', line 741

def register_seed_data(key, value)
  seed_data[key] = value
end

#register_seed_path_builder(&block) ⇒ Object



745
746
747
# File 'lib/plugin/instance.rb', line 745

def register_seed_path_builder(&block)
  DiscoursePluginRegistry.register_seed_path_builder(&block)
end

#register_seedfu_filter(filter = nil) ⇒ Object



660
661
662
# File 'lib/plugin/instance.rb', line 660

def register_seedfu_filter(filter = nil)
  DiscoursePluginRegistry.register_seedfu_filter(filter)
end

#register_seedfu_fixtures(paths) ⇒ Object



655
656
657
658
# File 'lib/plugin/instance.rb', line 655

def register_seedfu_fixtures(paths)
  paths = [paths] if !paths.kind_of?(Array)
  SeedFu.fixture_paths.concat(paths)
end

#register_service_worker(file, opts = nil) ⇒ Object



737
738
739
# File 'lib/plugin/instance.rb', line 737

def register_service_worker(file, opts = nil)
  service_workers << [File.join(File.dirname(path), "assets", file), opts]
end

#register_site_categories_callback(&block) ⇒ Object

Register a callback to add custom payload to Site#categories Example usage:

register_site_categories_callback do |categories|
  categories.each do |category|
    category[:some_field] = 'test'
  end
end


335
336
337
# File 'lib/plugin/instance.rb', line 335

def register_site_categories_callback(&block)
  Site.add_categories_callbacks(&block)
end

#register_site_setting_area(area) ⇒ Object

Site setting areas are a way to group site settings below the setting category level. This is useful for creating focused config areas that update a small selection of settings, and otherwise grouping related settings in the UI.



856
857
858
# File 'lib/plugin/instance.rb', line 856

def register_site_setting_area(area)
  DiscoursePluginRegistry.site_setting_areas << area
end

#register_stat(name, expose_via_api: false, &block) ⇒ Object

Allows the plugin to export additional site stats via the About class which will be shown on the /about route. The stats returned by the block should be in the following format (these four keys are required):

last_day: 1,
7_days: 10,
30_days: 100,
count: 1000

Only keys above will be shown on the /about page in the UI, but all stats will be shown on the /about.json route. For example take this usage:

register_stat(“chat_messages”) do

{ last_day: 1, "7_days" => 10, "30_days" => 100, count: 1000, previous_30_days: 150 }

end

In the UI we will show a table like this:

| 24h | 7 days | 30 days | all time|

Chat Messages | 1 | 10 | 100 | 1000 |

But the JSON will be like this:

"chat_messages_last_day": 1,
"chat_messages_7_days": 10,
"chat_messages_30_days": 100,
"chat_messages_count": 1000,



1129
1130
1131
1132
1133
1134
1135
# File 'lib/plugin/instance.rb', line 1129

def register_stat(name, expose_via_api: false, &block)
  # We do not want to register and display the same group multiple times.
  return if DiscoursePluginRegistry.stats.any? { |stat| stat.name == name }

  stat = Stat.new(name, expose_via_api: expose_via_api, &block)
  DiscoursePluginRegistry.register_stat(stat, self)
end

#register_summarization_strategy(strategy) ⇒ Object

Register an object that inherits from [Summarization::Base], which provides a way to summarize content. Staff can select which strategy to use through the ‘summarization_strategy` setting.



1230
1231
1232
1233
1234
1235
1236
1237
1238
# File 'lib/plugin/instance.rb', line 1230

def register_summarization_strategy(strategy)
  Discourse.deprecate(
    "register_summarization_strategy is deprecated. Summarization code is now moved to Discourse AI",
  )
  if !strategy.class.ancestors.include?(Summarization::Base)
    raise ArgumentError.new("Not a valid summarization strategy")
  end
  DiscoursePluginRegistry.register_summarization_strategy(strategy, self)
end

#register_svg_icon(icon) ⇒ Object



677
678
679
# File 'lib/plugin/instance.rb', line 677

def register_svg_icon(icon)
  DiscoursePluginRegistry.register_svg_icon(icon)
end

#register_topic_custom_field_type(name, type, max_length: nil) ⇒ Object

Applies to all sites in a multisite environment. Ignores plugin.enabled?



628
629
630
631
632
# File 'lib/plugin/instance.rb', line 628

def register_topic_custom_field_type(name, type, max_length: nil)
  reloadable_patch do |plugin|
    ::Topic.register_custom_field_type(name, type, max_length: max_length)
  end
end

#register_topic_list_preload_user_ids(&block) ⇒ Object

Allows to add more user IDs to the list of preloaded users. This can be useful to efficiently change the list of posters or participants. Example usage:

register_topic_list_preload_user_ids do |topics, user_ids, topic_list|
  user_ids << Discourse::SYSTEM_USER_ID
end


303
304
305
# File 'lib/plugin/instance.rb', line 303

def register_topic_list_preload_user_ids(&block)
  TopicList.on_preload_user_ids(&block)
end

#register_topic_thumbnail_size(size) ⇒ Object

Request a new size for topic thumbnails Will respect plugin enabled setting is enabled Size should be an array with two elements [max_width, max_height]



321
322
323
324
325
326
# File 'lib/plugin/instance.rb', line 321

def register_topic_thumbnail_size(size)
  if !(size.kind_of?(Array) && size.length == 2)
    raise ArgumentError.new("Topic thumbnail dimension is not valid")
  end
  DiscoursePluginRegistry.register_topic_thumbnail_size(size, self)
end

#register_topic_view_posts_filter(trigger, &block) ⇒ Object

Allows to define TopicView posts filters. Example usage:

TopicView.advanced_filter do |posts, opts|
  posts.where(wiki: true)
end


293
294
295
# File 'lib/plugin/instance.rb', line 293

def register_topic_view_posts_filter(trigger, &block)
  TopicView.add_custom_filter(trigger, &block)
end

#register_upload_in_use(&block) ⇒ Object



343
344
345
# File 'lib/plugin/instance.rb', line 343

def register_upload_in_use(&block)
  Upload.add_in_use_callback(&block)
end

#register_upload_unused(&block) ⇒ Object



339
340
341
# File 'lib/plugin/instance.rb', line 339

def register_upload_unused(&block)
  Upload.add_unused_callback(&block)
end

#register_user_custom_field_type(name, type, max_length: nil) ⇒ Object

Applies to all sites in a multisite environment. Ignores plugin.enabled?



649
650
651
652
653
# File 'lib/plugin/instance.rb', line 649

def register_user_custom_field_type(name, type, max_length: nil)
  reloadable_patch do |plugin|
    ::User.register_custom_field_type(name, type, max_length: max_length)
  end
end

#register_user_destroyer_on_content_deletion_callback(callback) ⇒ Object

Register a block that will be called when the UserDestroyer runs with the :delete_posts opt set to true. It’s important to note that the block will execute before any other :delete_posts actions, it allows us to manipulate flags before agreeing with them. For example, discourse-akismet makes use of this

Parameters:

  • callback (Block)

    to be called with the user, guardian, and the destroyer opts as arguments



1212
1213
1214
# File 'lib/plugin/instance.rb', line 1212

def register_user_destroyer_on_content_deletion_callback(callback)
  DiscoursePluginRegistry.register_user_destroyer_on_content_deletion_callback(callback, self)
end

#replace_flags(settings: ::FlagSettings.new, score_type_names: []) {|settings, next_flag_id| ... } ⇒ Object

Applies to all sites in a multisite environment. Ignores plugin.enabled?

Yields:

  • (settings, next_flag_id)


211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/plugin/instance.rb', line 211

def replace_flags(settings: ::FlagSettings.new, score_type_names: [])
  Discourse.deprecate(
    "replace flags should not be used as flags were moved to the database. Instead, a flag record should be added to the database. Alternatively, soon, the admin will be able to do this in the admin panel.",
  )
  next_flag_id = ReviewableScore.types.values.max + 1

  yield(settings, next_flag_id) if block_given?

  reloadable_patch do |plugin|
    ::PostActionType.replace_flag_settings(settings)
    ::ReviewableScore.reload_types
    ::ReviewableScore.add_new_types(score_type_names)
  end
end

#rescue_from(exception, &block) ⇒ Object



370
371
372
# File 'lib/plugin/instance.rb', line 370

def rescue_from(exception, &block)
  reloadable_patch { |plugin| ::ApplicationController.rescue_from(exception, &block) }
end

#root_dirObject



69
70
71
72
# File 'lib/plugin/instance.rb', line 69

def root_dir
  return if Rails.env.production?
  File.dirname(path)
end

#seed_dataObject



74
75
76
# File 'lib/plugin/instance.rb', line 74

def seed_data
  @seed_data ||= HashWithIndifferentAccess.new({})
end

#seed_fu_filter(filter = nil) ⇒ Object



78
79
80
# File 'lib/plugin/instance.rb', line 78

def seed_fu_filter(filter = nil)
  @seed_fu_filter = filter
end

#topic_view_post_custom_fields_allowlister(&block) ⇒ Object

Add a post_custom_fields_allowlister block to the TopicView, respecting if the plugin is enabled



436
437
438
439
440
441
442
# File 'lib/plugin/instance.rb', line 436

def topic_view_post_custom_fields_allowlister(&block)
  reloadable_patch do |plugin|
    ::TopicView.add_post_custom_fields_allowlister do |user, topic|
      plugin.enabled? ? block.call(user, topic) : []
    end
  end
end

#translate_emoji(from, to) ⇒ Object



755
756
757
# File 'lib/plugin/instance.rb', line 755

def translate_emoji(from, to)
  Plugin::CustomEmoji.translate(from, to)
end

#validate(klass, name, &block) ⇒ Object

Add validation method but check that the plugin is enabled



499
500
501
502
503
504
505
# File 'lib/plugin/instance.rb', line 499

def validate(klass, name, &block)
  klass = klass.to_s.classify.constantize
  klass.public_send(:define_method, name, &block)

  plugin = self
  klass.validate(name, if: -> { plugin.enabled? })
end

#visible?Boolean

Returns:

  • (Boolean)


137
138
139
# File 'lib/plugin/instance.rb', line 137

def visible?
  configurable? && !@hidden
end