Module: Plugins::CamaContactForm::MainHelper

Includes:
Recaptcha::Adapters::ViewMethods
Included in:
AdminFormsController, CamaContactForm, FrontController
Defined in:
app/helpers/plugins/cama_contact_form/main_helper.rb

Constant Summary collapse

CF_PLACEHOLDER =

Substitute every placeholder in ONE pass, so no replacement is ever scanned for the next placeholder. Substituting in sequence meant [label ci] was searched for in the field markup [ci] had just produced -- so a visitor typing the literal [label ci] into a message had the author's label spliced into the middle of the textarea that was echoing it back.

[ci] and [descr ci] keep first-occurrence semantics and [label ci] all-occurrence, which is what the three separate sub/sub/gsub calls did.

The block form of #gsub is used throughout: a String replacement would treat \1 or \\ in the value as a backreference and silently mangle it.

/\[(?:ci|label ci|descr ci)\]/
CF_REPEATABLE_PLACEHOLDERS =
['[label ci]'].freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(klass) ⇒ Object



3
4
5
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 3

def self.included(klass)
  klass.helper_method [:cama_form_element_bootstrap_object, :cama_form_shortcode] rescue "" # here your methods accessible from views
end

Instance Method Details

#cama_form_element_bootstrap_object(form, object, values) ⇒ Object

form contact with css bootstrap



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
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
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 125

def cama_form_element_bootstrap_object(form, object, values)
  html = ""
  object.each do |ob|
    ob[:label] = ob[:label].to_s.translate
    ob[:description] = ob[:description].to_s.translate
    r = {field: ob, form: form, template: (ob[:field_options][:template].present? ? ob[:field_options][:template] :  Plugins::CamaContactForm::CamaContactForm::field_template), custom_class: (ob[:field_options][:field_class] rescue nil), custom_attrs: {id: ob[:cid] }.merge((JSON.parse(ob[:field_options][:field_attributes]) rescue {})) }
    hooks_run("contact_form_item_render", r)
    ob = r[:field]
    ob[:custom_class] = r[:custom_class]
    ob[:custom_attrs] = r[:custom_attrs]
    # `cama_true?`, not `to_bool`: `to_bool` raises ArgumentError on anything outside its two
    # patterns, so a stored `required` of `maybe` was a 500 on every visit to the page.
    ob[:custom_attrs][:required] = 'true' if ob[:required].to_s.cama_true?
    field_options = ob[:field_options]
    for_name = ob[:label].to_s
    f_name = "fields[#{ob[:cid]}]"
    cid = ob[:cid].to_sym

    temp2 = ""

    # Both a redisplayed submission and the author's default_value render verbatim: each was
    # validated before it could be stored or stashed, so neither can carry anything the submitter
    # was not permitted to write.
    current_value = values[cid] || ob[:default_value].to_s.translate

    case ob[:field_type].to_s
      when 'paragraph','textarea'
        temp2 = "<textarea #{cf_attrs(ob[:custom_attrs])} name=\"#{f_name}\" maxlength=\"#{field_options[:maxlength] || 500}\"  class=\"#{ob[:custom_class].presence || 'form-control'}  \">#{current_value}</textarea>"
      when 'radio'
        temp2=  cama_form_select_multiple_bootstrap(ob, ob[:label], ob[:field_type],values)
      when 'checkboxes'
        temp2=  cama_form_select_multiple_bootstrap(ob, ob[:label], "checkbox",values)
      when 'submit'
        temp2 = "<button #{cf_attrs(ob[:custom_attrs])} type=\"#{ob[:field_type]}\" name=\"#{f_name}\"  class=\"#{ob[:custom_class].presence || 'btn btn-default'}\">#{ob[:label]}</button>"
      when 'button'
        temp2 = "<button #{cf_attrs(ob[:custom_attrs])} type='button' name=\"#{f_name}\" class=\"#{ob[:custom_class].presence || 'btn btn-default'}\">#{ob[:label]}</button>"
      when 'reset_button'
        temp2 = "<button #{cf_attrs(ob[:custom_attrs])} type='reset' name=\"#{f_name}\" class=\"#{ob[:custom_class].presence || 'btn btn-default'}\">#{ob[:label]}</button>"
      when 'text', 'website', 'email'
        class_type = ""
        class_type = "railscf-field-#{ob[:field_type]}" if ob[:field_type]=="website"
        class_type = "railscf-field-#{ob[:field_type]}" if ob[:field_type]=="email"
        temp2 = "<input #{cf_attrs(ob[:custom_attrs])} type=\"#{ob[:field_type]}\" value=\"#{current_value}\" name=\"#{f_name}\"  class=\"#{ob[:custom_class].presence || 'form-control'} #{class_type}\">"
      when 'captcha'
        if form.recaptcha_enabled?
          temp2 = recaptcha_tags
        else
          # The one field type whose attributes do not go through cf_attrs: cama_captcha_tag builds
          # its markup with Rails' `tag`, which escapes values and rewrites malformed names. That is
          # a documented exception to this file's verbatim contract rather than a gap in it -- `tag`
          # is the stricter of the two, so a trusted author's quoted value is escaped here and a
          # malformed attribute name is mangled rather than emitted. Left as it is deliberately:
          # reproducing cf_attrs would mean rebuilding the helper's output by string surgery, which
          # is a real risk of breaking the captcha for a consistency gain and no security gain.
          temp2 = cama_captcha_tag(5, {}, {class: "#{ob[:custom_class].presence || 'form-control'} field-captcha required"}.merge(ob[:custom_attrs]))
        end
      when 'file'
        temp2 = "<input multiple=\"multiple\" type=\"file\" value=\"\" name=\"#{f_name}[]\" #{cf_attrs(ob[:custom_attrs])} class=\"#{ob[:custom_class].presence || 'form-control'}\">"
      when 'dropdown'
        temp2 = cama_form_select_multiple_bootstrap(ob, ob[:label], "select",values)
      else
    end
    r[:template] = cf_substitute(r[:template],
                                 '[ci]' => temp2,
                                 '[descr ci]' => field_options[:description].to_s.translate,
                                 '[label ci]' => for_name).sub('<p></p>', '')
    html += r[:template]
  end
  html
end

#cama_form_select_multiple_bootstrap(ob, title, type, values) ⇒ Object



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
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
238
239
240
241
242
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 196

def cama_form_select_multiple_bootstrap(ob, title, type, values)
  # Defensive, because a form saved before the gate required a well-formed option list carries
  # whatever it carries -- and `nil.each`, or `op[:label]` on a String, is a 500 on every visit to
  # the page. `Array()` alone will not do: on a Hash it yields key/value pairs.
  raw_options = ob[:field_options][:options]
  options = (raw_options.is_a?(Hash) ? raw_options.values : Array(raw_options))
            .select { |op| op.is_a?(Hash) }
  include_other_option = ob[:field_options][:include_other_option]
  other_input = ""

  f_name = "fields[#{ob[:cid]}]"
  cid = ob[:cid].to_sym
  html = ""

  custom_class = ob[:custom_class].to_s

  if type == "radio" || type == "checkbox"
    other_input = (include_other_option)? "<div class=\"#{type} #{custom_class}\"> <label for=\"#{ob[:cid]}\"><input id=\"#{ob[:cid]}-other\" type=\"#{type}\" name=\"#{title.downcase}[]\" class=\"\">Other <input type=\"text\" /></label></div>" : " "
  else
    html = "<select #{cf_attrs(ob[:custom_attrs])} name=\"#{f_name}\" class=\"#{custom_class}\">"
  end

  options.each do |op|
    label = op[:label].to_s.translate
    if type == "radio" || type == "checkbox"
      html += "<div class=\"#{type} #{custom_class}\">
                  <label for=\"#{ob[:cid]}\">
                    <input #{cf_attrs(ob[:custom_attrs])} type=\"#{type}\" #{'checked' if op[:checked].to_s.cama_true?} name=\"#{f_name}[]\" class=\"\" value=\"#{label.downcase}\">
                    #{label}
                  </label>
                </div>"
    else
      # A dropdown submits the label lowercased with spaces collapsed to underscores. Derived once
      # so the emitted value and the `selected` comparison cannot drift apart -- but only here:
      # radio and checkbox have always submitted the plain lowercased label, and unifying the two
      # would silently change what every existing response row is compared against.
      option_value = label.downcase.gsub(" ", "_")
      html += "<option  value=\"#{option_value}\" #{"selected" if option_value == values[cid] || op[:checked].to_s.cama_true? } >#{label}</option>"
    end
  end

  if type == "radio" || type == "checkbox"
    html += other_input
  else
    html += " </select>"
  end
end

#cama_form_shortcode(slug) ⇒ Object

HTML ==================

This returns the format of the plugin shortcode.



71
72
73
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 71

def cama_form_shortcode(slug)
  "[forms slug=#{slug}]"
end

#cf_attrs(attrs) ⇒ Object

Nothing in this file escapes anything, by design. Every value it interpolates -- an author's labels, descriptions, classes, default values, wrappers and templates, a visitor's redisplayed submission, and the structural identifiers the form builder generates -- reaches the page VERBATIM.

That is safe because it is enforced at the gate rather than at the sink. The admin controller refuses to store content an untrusted author may not write, and refuses structurally malformed values from anyone; the front controller refuses a submission carrying anything malign and echoes it back not at all. Stored content therefore always equals written content, so rendering it verbatim introduces nothing the writer did not put there -- and an author holding :manage, :contact_form_unfiltered_html can put markup, or script, anywhere in a form and have guests receive it exactly as written.

Emit a field's custom attributes verbatim.

Camaleon's Hash#to_attr_format is deliberately NOT used here. That method escapes values and drops keys that are not valid attribute names, which is exactly right for its other callers -- plugins and themes handing it data from who-knows-where -- but wrong for this one. field_attributes is authored content: it is validated when the form is saved, and an author holding the unfiltered-HTML grant is entitled to emit an event handler through it.



95
96
97
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 95

def cf_attrs(attrs)
  (attrs || {}).map { |key, value| "#{key} = \"#{value}\"" }.join(' ')
end

#cf_substitute(template, replacements) ⇒ Object



112
113
114
115
116
117
118
119
120
121
122
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 112

def cf_substitute(template, replacements)
  seen = Hash.new(0)
  template.to_s.gsub(CF_PLACEHOLDER) do |placeholder|
    seen[placeholder] += 1
    if seen[placeholder] == 1 || CF_REPEATABLE_PLACEHOLDERS.include?(placeholder)
      replacements.fetch(placeholder, placeholder)
    else
      placeholder
    end
  end
end

#contact_form_admin_before_loadObject



57
58
59
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 57

def contact_form_admin_before_load
  admin_menu_append_menu_item("settings", {icon: "envelope-o", title: t('plugins.cama_contact_form.title', default: 'Contact Form'), url: admin_plugins_cama_contact_form_admin_forms_path, datas: "data-intro='This plugin permit you to create you contact forms with desired fields and paste your short_code in any content.' data-position='right'"})
end

#contact_form_app_before_loadObject



61
62
63
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 61

def contact_form_app_before_load
  shortcode_add('forms', plugin_view("forms_shorcode"), "This is a shortocode for contact form to permit you to put your contact form in any content. Sample: [forms slug='key-for-my-form']")
end

#contact_form_front_before_loadObject



65
66
67
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 65

def contact_form_front_before_load

end

#contact_form_on_active(plugin) ⇒ Object

here all actions on going to active you can run sql commands like this: results = ActiveRecord::Base.connection.execute(query); plugin: plugin model



47
48
49
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 47

def contact_form_on_active(plugin)

end

#contact_form_on_destroy(plugin) ⇒ Object

here all actions on plugin destroying plugin: plugin model



39
40
41
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 39

def contact_form_on_destroy(plugin)

end

#contact_form_on_export(args) ⇒ Object



7
8
9
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 7

def contact_form_on_export(args)
  args[:obj][:plugins][self_plugin_key] = JSON.parse(current_site.contact_forms.to_json(:include => [:responses]))
end

#contact_form_on_import(args) ⇒ Object



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 11

def contact_form_on_import(args)
  plugins = args[:data][:plugins]
  if plugins[self_plugin_key.to_sym].present?
    plugins[self_plugin_key.to_sym].each do |contact|
      unless current_site.contact_forms.where(slug: contact[:slug]).first.present?
        sba_data = ActionController::Parameters.new(contact)
        contact_new = current_site.contact_forms.new(sba_data.permit(:name, :slug, :count, :description, :value, :settings))
        if contact_new.save!
          if contact[:get_field_groups] # save group fields
            save_field_group(contact_new, contact[:get_field_groups])
          end
          save_field_values(contact_new, contact[:field_values])

          if contact[:responses].present? # saving responses for this contact
            contact[:responses].each do |response|
              sba_data = ActionController::Parameters.new(response)
              contact_new.responses.create!(sba_data.permit(:name, :slug, :count, :description, :value, :settings))
            end
          end
          args[:messages] << "Saved Plugin Contact Form: #{contact_new.name}"
        end
      end
    end
  end
end

#contact_form_on_inactive(plugin) ⇒ Object

here all actions on going to inactive plugin: plugin model



53
54
55
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 53

def contact_form_on_inactive(plugin)

end