module Recaptcha::Helpers

Constants

DEFAULT_ERRORS

Public Class Methods

invisible_recaptcha_tags(custom) click to toggle source
# File lib/recaptcha/helpers.rb, line 84
def self.invisible_recaptcha_tags(custom)
  options = {callback: 'invisibleRecaptchaSubmit', ui: :button}.merge(custom)
  text = options.delete(:text)
  html, tag_attributes = components(options.dup)
  html << default_callback(options) if default_callback_required?(options)

  case options[:ui]
  when :button
    html << %(<button type="submit" #{tag_attributes}>#{text}</button>\n)
  when :invisible
    html << %(<div data-size="invisible" #{tag_attributes}></div>\n)
  when :input
    html << %(<input type="submit" #{tag_attributes} value="#{text}"/>\n)
  else
    raise(RecaptchaError, "ReCAPTCHA ui `#{options[:ui]}` is not valid.")
  end
  html.respond_to?(:html_safe) ? html.html_safe : html
end
recaptcha_tags(options) click to toggle source
# File lib/recaptcha/helpers.rb, line 41
    def self.recaptcha_tags(options)
      if options.key?(:stoken)
        raise(RecaptchaError, "Secure Token is deprecated. Please remove 'stoken' from your calls to recaptcha_tags.")
      end
      if options.key?(:ssl)
        raise(RecaptchaError, "SSL is now always true. Please remove 'ssl' from your calls to recaptcha_tags.")
      end

      noscript = options.delete(:noscript)

      html, tag_attributes, fallback_uri = components(options.dup)
      html << %(<div #{tag_attributes}></div>\n)

      if noscript != false
        html << <<-HTML
          <noscript>
            <div>
              <div style="width: 302px; height: 422px; position: relative;">
                <div style="width: 302px; height: 422px; position: absolute;">
                  <iframe
                    src="#{fallback_uri}"
                    name="ReCAPTCHA"
                    style="width: 302px; height: 422px; border-style: none; border: 0; overflow: hidden;">
                  </iframe>
                </div>
              </div>
              <div style="width: 300px; height: 60px; border-style: none;
                bottom: 12px; left: 25px; margin: 0px; padding: 0px; right: 25px;
                background: #f9f9f9; border: 1px solid #c1c1c1; border-radius: 3px;">
                <textarea id="g-recaptcha-response" name="g-recaptcha-response"
                  class="g-recaptcha-response"
                  style="width: 250px; height: 40px; border: 1px solid #c1c1c1;
                  margin: 10px 25px; padding: 0px; resize: none;">
                </textarea>
              </div>
            </div>
          </noscript>
        HTML
      end

      html.respond_to?(:html_safe) ? html.html_safe : html
    end
recaptcha_v3(options = {}) click to toggle source
# File lib/recaptcha/helpers.rb, line 10
def self.recaptcha_v3(options = {})
  site_key = options[:site_key] ||= Recaptcha.configuration.site_key!
  action = options.delete(:action) || raise(Recaptcha::RecaptchaError, 'action is required')
  id   = options.delete(:id)   || "g-recaptcha-response-" + dasherize_action(action)
  name = options.delete(:name) || "g-recaptcha-response[#{action}]"
  options[:render] = site_key
  options[:script_async] ||= false
  options[:script_defer] ||= false
  element = options.delete(:element)
  element = element == false ? false : :input
  if element == :input
    callback = options.delete(:callback) || recaptcha_v3_default_callback_name(action)
  end
  options[:class] = "g-recaptcha-response #{options[:class]}"

  html, tag_attributes = components(options)
  if recaptcha_v3_inline_script?(options)
    html << recaptcha_v3_inline_script(site_key, action, callback, id, options)
  end
  case element
  when :input
    html << %(<input type="hidden" name="#{name}" id="#{id}" #{tag_attributes}/>\n)
  when false
    # No tag
    nil
  else
    raise(RecaptchaError, "ReCAPTCHA element `#{options[:element]}` is not valid.")
  end
  html.respond_to?(:html_safe) ? html.html_safe : html
end
recaptcha_v3_async_execute_function_name(action) click to toggle source

Returns the name of an async JavaScript function that executes the reCAPTCHA code.

# File lib/recaptcha/helpers.rb, line 237
def self.recaptcha_v3_async_execute_function_name(action)
  "#{recaptcha_v3_execute_function_name(action)}Async"
end
recaptcha_v3_default_callback_name(action) click to toggle source
# File lib/recaptcha/helpers.rb, line 241
def self.recaptcha_v3_default_callback_name(action)
  "setInputWithRecaptchaResponseTokenFor#{sanitize_action_for_js(action)}"
end
recaptcha_v3_execute_function_name(action) click to toggle source

Returns the name of the JavaScript function that actually executes the reCAPTCHA code (calls grecaptcha.execute). You can call it again later to reset it.

# File lib/recaptcha/helpers.rb, line 232
def self.recaptcha_v3_execute_function_name(action)
  "executeRecaptchaFor#{sanitize_action_for_js(action)}"
end
to_error_message(key) click to toggle source
# File lib/recaptcha/helpers.rb, line 103
def self.to_error_message(key)
  default = DEFAULT_ERRORS.fetch(key) { raise ArgumentError "Unknown reCAPTCHA error - #{key}" }
  to_message("recaptcha.errors.#{key}", default)
end
to_message(key, default) click to toggle source
# File lib/recaptcha/helpers.rb, line 109
def self.to_message(key, default)
  I18n.translate(key, default: default)
end

Private Class Methods

components(options) click to toggle source
# File lib/recaptcha/helpers.rb, line 118
                     def self.components(options)
  html = +''
  attributes = {}
  fallback_uri = +''

  options = options.dup
  env = options.delete(:env)
  class_attribute = options.delete(:class)
  site_key = options.delete(:site_key)
  hl = options.delete(:hl)
  onload = options.delete(:onload)
  render = options.delete(:render)
  script_async = options.delete(:script_async)
  script_defer = options.delete(:script_defer)
  nonce = options.delete(:nonce)
  skip_script = (options.delete(:script) == false) || (options.delete(:external_script) == false)
  ui = options.delete(:ui)

  data_attribute_keys = [:badge, :theme, :type, :callback, :expired_callback, :error_callback, :size]
  data_attribute_keys << :tabindex unless ui == :button
  data_attributes = {}
  data_attribute_keys.each do |data_attribute|
    value = options.delete(data_attribute)
    data_attributes["data-#{data_attribute.to_s.tr('_', '-')}"] = value if value
  end

  unless Recaptcha.skip_env?(env)
    site_key ||= Recaptcha.configuration.site_key!
    script_url = Recaptcha.configuration.api_server_url
    query_params = hash_to_query(
      hl: hl,
      onload: onload,
      render: render
    )
    script_url += "?#{query_params}" unless query_params.empty?
    async_attr = "async" if script_async != false
    defer_attr = "defer" if script_defer != false
    nonce_attr = " nonce='#{nonce}'" if nonce
    html << %(<script src="#{script_url}" #{async_attr} #{defer_attr} #{nonce_attr}></script>\n) unless skip_script
    fallback_uri = %(#{script_url.chomp(".js")}/fallback?k=#{site_key})
    attributes["data-sitekey"] = site_key
    attributes.merge! data_attributes
  end

  # The remaining options will be added as attributes on the tag.
  attributes["class"] = "g-recaptcha #{class_attribute}"
  tag_attributes = attributes.merge(options).map { |k, v| %(#{k}="#{v}") }.join(" ")

  [html, tag_attributes, fallback_uri]
end
dasherize_action(action) click to toggle source

Returns a dasherized string that is safe for use as an HTML ID dasherize_action('my/action') => 'my-action'

# File lib/recaptcha/helpers.rb, line 289
                     def self.dasherize_action(action)
  action.to_s.gsub(/\W/, '-').dasherize
end
default_callback(options = {}) click to toggle source

v2

# File lib/recaptcha/helpers.rb, line 247
                         def self.default_callback(options = {})
      nonce = options[:nonce]
      nonce_attr = " nonce='#{nonce}'" if nonce

      <<-HTML
        <script#{nonce_attr}>
          var invisibleRecaptchaSubmit = function () {
            var closestForm = function (ele) {
              var curEle = ele.parentNode;
              while (curEle.nodeName !== 'FORM' && curEle.nodeName !== 'BODY'){
                curEle = curEle.parentNode;
              }
              return curEle.nodeName === 'FORM' ? curEle : null
            };

            var eles = document.getElementsByClassName('g-recaptcha');
            if (eles.length > 0) {
              var form = closestForm(eles[0]);
              if (form) {
                form.submit();
              }
            }
          };
        </script>
      HTML
    end
default_callback_required?(options) click to toggle source
# File lib/recaptcha/helpers.rb, line 274
                     def self.default_callback_required?(options)
  options[:callback] == 'invisibleRecaptchaSubmit' &&
  !Recaptcha.skip_env?(options[:env]) &&
  options[:script] != false &&
  options[:inline_script] != false
end
hash_to_query(hash) click to toggle source
# File lib/recaptcha/helpers.rb, line 293
                     def self.hash_to_query(hash)
  hash.delete_if { |_, val| val.nil? || val.empty? }.to_a.map { |pair| pair.join('=') }.join('&')
end
recaptcha_v3_define_default_callback(callback) click to toggle source
# File lib/recaptcha/helpers.rb, line 212
                         def self.recaptcha_v3_define_default_callback(callback)
      <<-HTML
          var #{callback} = function(id, token) {
            var element = document.getElementById(id);
            element.value = token;
          }
        </script>
      HTML
    end
recaptcha_v3_define_default_callback?(callback, action, options) click to toggle source

Returns true if we should be adding the default callback. That is, if the given callback name is the default callback name (for the given action) and we are not skipping inline scripts for any reason.

# File lib/recaptcha/helpers.rb, line 225
                     def self.recaptcha_v3_define_default_callback?(callback, action, options)
  callback == recaptcha_v3_default_callback_name(action) &&
  recaptcha_v3_inline_script?(options)
end
recaptcha_v3_inline_script(site_key, action, callback, id, options = {}) click to toggle source

Renders a script that calls `grecaptcha.execute` for the given `site_key` and `action` and calls the `callback` with the resulting response token.

# File lib/recaptcha/helpers.rb, line 173
                         def self.recaptcha_v3_inline_script(site_key, action, callback, id, options = {})
      nonce = options[:nonce]
      nonce_attr = " nonce='#{nonce}'" if nonce

      <<-HTML
        <script#{nonce_attr}>
          // Define function so that we can call it again later if we need to reset it
          function #{recaptcha_v3_execute_function_name(action)}() {
            grecaptcha.ready(function() {
              grecaptcha.execute('#{site_key}', {action: '#{action}'}).then(function(token) {
                //console.log('#{id}', token)
                #{callback}('#{id}', token)
              });
            });
          };
          // Invoke immediately
          #{recaptcha_v3_execute_function_name(action)}()

          // Async variant so you can await this function from another async function (no need for
          // an explicit callback function then!)
          async function #{recaptcha_v3_async_execute_function_name(action)}() {
            return new Promise((resolve, reject) => {
              grecaptcha.ready(async function() {
                resolve(await grecaptcha.execute('#{site_key}', {action: '#{action}'}))
              });
            })
          };

          #{recaptcha_v3_define_default_callback(callback) if recaptcha_v3_define_default_callback?(callback, action, options)}
        </script>
      HTML
    end
recaptcha_v3_inline_script?(options) click to toggle source
# File lib/recaptcha/helpers.rb, line 206
                     def self.recaptcha_v3_inline_script?(options)
  !Recaptcha.skip_env?(options[:env]) &&
  options[:script] != false &&
  options[:inline_script] != false
end
sanitize_action_for_js(action) click to toggle source

Returns a camelized string that is safe for use in a JavaScript variable/function name. sanitize_action_for_js('my/action') => 'MyAction'

# File lib/recaptcha/helpers.rb, line 283
                     def self.sanitize_action_for_js(action)
  action.to_s.gsub(/\W/, '_').camelize
end