lib/simple_form.rb



# frozen_string_literal: true
require 'action_view'
require 'action_pack'
require 'simple_form/action_view_extensions/form_helper'
require 'simple_form/action_view_extensions/builder'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/hash/reverse_merge'

module SimpleForm
  extend ActiveSupport::Autoload

  autoload :Helpers
  autoload :Wrappers

  eager_autoload do
    autoload :Components
    autoload :ErrorNotification
    autoload :FormBuilder
    autoload :Inputs
  end

  def self.eager_load!
    super
    SimpleForm::Inputs.eager_load!
    SimpleForm::Components.eager_load!
  end

  CUSTOM_INPUT_DEPRECATION_WARN = <<-WARN
%{name} method now accepts a `wrapper_options` argument. The method definition without the argument is deprecated and will be removed in the next Simple Form version. Change your code from:

    def %{name}

to

    def %{name}(wrapper_options)

See https://github.com/heartcombo/simple_form/pull/997 for more information.
  WARN

  FILE_METHODS_DEPRECATION_WARN = <<-WARN
[SIMPLE_FORM] SimpleForm.file_methods is deprecated and has no effect.

Since version 5, Simple Form now supports automatically discover of file inputs for the following Gems: activestorage, carrierwave, paperclip, refile and shrine.
If you are using a custom method that is not from one of the supported Gems, please change your forms to pass the input type explicitly:

    <%= form.input :avatar, as: :file %>

See http://blog.plataformatec.com.br/2019/09/incorrect-access-control-in-simple-form-cve-2019-16676 for more information.
  WARN

  @@configured = false

  def self.configured? #:nodoc:
    @@configured
  end

  ## CONFIGURATION OPTIONS

  # Method used to tidy up errors.
  mattr_accessor :error_method
  @@error_method = :first

  # Default tag used for error notification helper.
  mattr_accessor :error_notification_tag
  @@error_notification_tag = :p

  # CSS class to add for error notification helper.
  mattr_accessor :error_notification_class
  @@error_notification_class = :error_notification

  # Series of attemps to detect a default label method for collection.
  mattr_accessor :collection_label_methods
  @@collection_label_methods = %i[to_label name title to_s]

  # Series of attemps to detect a default value method for collection.
  mattr_accessor :collection_value_methods
  @@collection_value_methods = %i[id to_s]

  # You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none.
  mattr_accessor :collection_wrapper_tag
  @@collection_wrapper_tag = nil

  # You can define the class to use on all collection wrappers, defaulting to none.
  mattr_accessor :collection_wrapper_class
  @@collection_wrapper_class = nil

  # You can wrap each item in a collection of radio/check boxes with a tag,
  # defaulting to span. Please note that when using :boolean_style = :nested,
  # SimpleForm will force this option to be a :label.
  mattr_accessor :item_wrapper_tag
  @@item_wrapper_tag = :span

  # You can define the class to use on all item wrappers, defaulting to none.
  mattr_accessor :item_wrapper_class
  @@item_wrapper_class = nil

  # How the label text should be generated altogether with the required text.
  mattr_accessor :label_text
  @@label_text = ->(label, required, explicit_label) { "#{required} #{label}" }

  # You can define the class to be used on all labels. Defaults to none.
  mattr_accessor :label_class
  @@label_class = nil

  # Define the way to render check boxes / radio buttons with labels.
  #   inline: input + label (default)
  #   nested: label > input
  mattr_accessor :boolean_style
  @@boolean_style = :inline

  # DEPRECATED: You can define the class to be used on all forms. Default is
  # simple_form.
  mattr_reader :form_class
  @@form_class = :simple_form

  # You can define the default class to be used on all forms. Can be overridden
  # with `html: { :class }`. Defaults to none.
  mattr_accessor :default_form_class
  @@default_form_class = nil

  # You can define which elements should obtain additional classes.
  mattr_accessor :generate_additional_classes_for
  @@generate_additional_classes_for = %i[wrapper label input]

  # Whether attributes are required by default or not.
  mattr_accessor :required_by_default
  @@required_by_default = true

  # Tell browsers whether to use default HTML5 validations (novalidate option).
  mattr_accessor :browser_validations
  @@browser_validations = true

  # Custom mappings for input types. This should be a hash containing a regexp
  # to match as key, and the input type that will be used when the field name
  # matches the regexp as value, such as { /count/ => :integer }.
  mattr_accessor :input_mappings
  @@input_mappings = nil

  # Custom wrappers for input types. This should be a hash containing an input
  # type as key and the wrapper that will be used for all inputs with specified type.
  # e.g { string: :string_wrapper, boolean: :boolean_wrapper }
  # You can also set a wrapper mapping per form basis.
  # e.g simple_form_for(@foo, wrapper_mappings: { check_boxes: :bootstrap_checkbox })
  mattr_accessor :wrapper_mappings
  @@wrapper_mappings = nil

  # Namespaces where SimpleForm should look for custom input classes that override
  # default inputs. Namespaces are given as string to allow lazy loading inputs.
  # e.g. config.custom_inputs_namespaces << "CustomInputs"
  #      will try to find CustomInputs::NumericInput when an :integer
  #      field is called.
  mattr_accessor :custom_inputs_namespaces
  @@custom_inputs_namespaces = []

  # Default priority for time_zone inputs.
  mattr_accessor :time_zone_priority
  @@time_zone_priority = nil

  # Default priority for country inputs.
  mattr_accessor :country_priority
  @@country_priority = nil

  # When off, do not use translations in labels. Disabling translation in
  # hints and placeholders can be done manually in the wrapper API.
  mattr_accessor :translate_labels
  @@translate_labels = true

  # Automatically discover new inputs in Rails' autoload path.
  mattr_accessor :inputs_discovery
  @@inputs_discovery = true

  # Cache SimpleForm inputs discovery.
  mattr_accessor :cache_discovery
  @@cache_discovery = defined?(Rails.env) && !Rails.env.development?

  # Adds a class to each generated button, mostly for compatiblity.
  mattr_accessor :button_class
  @@button_class = 'button'

  # Override the default ActiveModelHelper behaviour of wrapping the input.
  # This gets taken care of semantically by adding an error class to the wrapper tag
  # containing the input.
  mattr_accessor :field_error_proc
  @@field_error_proc = proc do |html_tag, instance_tag|
    html_tag
  end

  # Adds a class to each generated inputs
  mattr_accessor :input_class
  @@input_class = nil

  # Defines if an input wrapper class should be included or not
  mattr_accessor :include_default_input_wrapper_class
  @@include_default_input_wrapper_class = true

  # Define the default class of the input wrapper of the boolean input.
  mattr_accessor :boolean_label_class
  @@boolean_label_class = 'checkbox'

  ## WRAPPER CONFIGURATION
  # The default wrapper to be used by the FormBuilder.
  mattr_accessor :default_wrapper
  @@default_wrapper = :default
  @@wrappers = {} #:nodoc:

  mattr_accessor :i18n_scope
  @@i18n_scope = 'simple_form'

  mattr_accessor :input_field_error_class
  @@input_field_error_class = nil

  mattr_accessor :input_field_valid_class
  @@input_field_valid_class = nil

  # Retrieves a given wrapper
  def self.wrapper(name)
    @@wrappers[name.to_s] or raise WrapperNotFound, "Couldn't find wrapper with name #{name}"
  end

  # Raised when fails to find a given wrapper name
  class WrapperNotFound < StandardError
  end

  # Define a new wrapper using SimpleForm::Wrappers::Builder
  # and store it in the given name.
  def self.wrappers(*args, &block)
    if block_given?
      options                 = args.extract_options!
      name                    = args.first || :default
      @@wrappers[name.to_s]   = build(options, &block)
    else
      @@wrappers
    end
  end

  # Builds a new wrapper using SimpleForm::Wrappers::Builder.
  def self.build(options = {})
    options[:tag] = :div if options[:tag].nil?
    builder = SimpleForm::Wrappers::Builder.new(options)
    yield builder
    SimpleForm::Wrappers::Root.new(builder.to_a, options)
  end

  wrappers class: :input, hint_class: :field_with_hint, error_class: :field_with_errors, valid_class: :field_without_errors do |b|
    b.use :html5

    b.use :min_max
    b.use :maxlength
    b.use :minlength
    b.use :placeholder
    b.optional :pattern
    b.optional :readonly

    b.use :label_input
    b.use :hint,  wrap_with: { tag: :span, class: :hint }
    b.use :error, wrap_with: { tag: :span, class: :error }
  end

  def self.additional_classes_for(component)
    generate_additional_classes_for.include?(component) ? yield : []
  end

  ## SETUP

  def self.default_input_size=(*)
    ActiveSupport::Deprecation.warn "[SIMPLE_FORM] SimpleForm.default_input_size= is deprecated and has no effect", caller
  end

  def self.form_class=(value)
    ActiveSupport::Deprecation.warn "[SIMPLE_FORM] SimpleForm.form_class= is deprecated and will be removed in 4.x. Use SimpleForm.default_form_class= instead", caller
    @@form_class = value
  end

  def self.file_methods=(file_methods)
    ActiveSupport::Deprecation.warn(FILE_METHODS_DEPRECATION_WARN, caller)
    @@file_methods = file_methods
  end

  def self.file_methods
    ActiveSupport::Deprecation.warn(FILE_METHODS_DEPRECATION_WARN, caller)
    @@file_methods
  end

  # Default way to setup Simple Form. Run rails generate simple_form:install
  # to create a fresh initializer with all configuration values.
  def self.setup
    @@configured = true
    yield self
  end

  # Includes a component to be used by Simple Form. Methods defined in a
  # component will be exposed to be used in the wrapper as Simple::Components
  #
  # Examples
  #
  #    # The application needs to tell where the components will be.
  #    Dir[Rails.root.join('lib/components/**/*.rb')].each { |f| require f }
  #
  #    # Create a custom component in the path specified above.
  #    # lib/components/input_group_component.rb
  #    module InputGroupComponent
  #      def prepend
  #        ...
  #      end
  #
  #      def append
  #        ...
  #      end
  #    end
  #
  #    SimpleForm.setup do |config|
  #      # Create a wrapper using the custom component.
  #      config.wrappers :input_group, tag: :div, error_class: :error do |b|
  #        b.use :label
  #        b.optional :prepend
  #        b.use :input
  #        b.use :append
  #      end
  #    end
  #
  #    # Using the custom component in the form.
  #    <%= simple_form_for @blog, wrapper: input_group do |f| %>
  #      <%= f.input :title, prepend: true %>
  #    <% end %>
  #
  def self.include_component(component)
    if Module === component
      SimpleForm::Inputs::Base.include(component)
    else
      raise TypeError, "SimpleForm.include_component expects a module but got: #{component.class}"
    end
  end
end

require 'simple_form/railtie' if defined?(Rails)