Module: Plugins::CamaContactForm::MainHelper
- Includes:
- Recaptcha::Adapters::ViewMethods
- Included in:
- AdminFormsController, CamaContactForm, FrontController
- Defined in:
- app/helpers/plugins/cama_contact_form/main_helper.rb
Overview
View helpers that render a form's fields to Bootstrap markup for the [forms] shortcode.
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
\1or\\in the value as a backreference and silently mangle it. /\[(?:ci|label ci|descr ci)\]/- CF_REPEATABLE_PLACEHOLDERS =
['[label ci]'].freeze
- RADIO_CHECKBOX_CONTROL_TYPES =
Field control types rendered as a group of tags rather than a
%w[radio checkbox].freeze
Class Method Summary collapse
Instance Method Summary collapse
-
#cama_form_element_bootstrap_object(form, object, values) ⇒ Object
form contact with css bootstrap.
- #cama_form_select_multiple_bootstrap(obj, title, type, values) ⇒ Object
-
#cama_form_shortcode(slug) ⇒ Object
HTML ================== This returns the format of the plugin shortcode.
-
#cf_attrs(attrs) ⇒ Object
Nothing in this file escapes anything, by design.
- #cf_substitute(template, replacements) ⇒ Object
- #contact_form_admin_before_load ⇒ Object
- #contact_form_app_before_load ⇒ Object
- #contact_form_front_before_load ⇒ Object
-
#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.
-
#contact_form_on_destroy(plugin) ⇒ Object
here all actions on plugin destroying plugin: plugin model.
- #contact_form_on_export(args) ⇒ Object
- #contact_form_on_import(args) ⇒ Object
-
#contact_form_on_inactive(plugin) ⇒ Object
here all actions on going to inactive plugin: plugin model.
Class Method Details
.included(klass) ⇒ Object
7 8 9 10 11 12 |
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 7 def self.included(klass) klass.helper_method %i[cama_form_element_bootstrap_object cama_form_shortcode] rescue StandardError '' # here your methods accessible from views end |
Instance Method Details
#cama_form_element_bootstrap_object(form, object, values) ⇒ Object
form contact with css bootstrap
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 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 132 def cama_form_element_bootstrap_object(form, object, values) html = '' object.each do |obj| obj[:label] = obj[:label].to_s.translate obj[:description] = obj[:description].to_s.translate template = obj[:field_options][:template].presence || Plugins::CamaContactForm::CamaContactForm.field_template r = { field: obj, form: form, template: template, custom_class: begin obj[:field_options][:field_class] rescue StandardError nil end, custom_attrs: { id: obj[:cid] }.merge(begin JSON.parse(obj[:field_options][:field_attributes]) rescue StandardError {} end) } hooks_run('contact_form_item_render', r) obj = r[:field] obj[:custom_class] = r[:custom_class] obj[: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. obj[:custom_attrs][:required] = 'true' if obj[:required].to_s.cama_true? = obj[:field_options] for_name = obj[:label].to_s f_name = "fields[#{obj[:cid]}]" cid = obj[: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] || obj[:default_value].to_s.translate case obj[:field_type].to_s when 'paragraph', 'textarea' temp2 = "<textarea #{cf_attrs(obj[:custom_attrs])} name=\"#{f_name}\" " \ "maxlength=\"#{[:maxlength] || 500}\" " \ "class=\"#{obj[:custom_class].presence || 'form-control'} \">#{current_value}</textarea>" when 'radio' temp2 = cama_form_select_multiple_bootstrap(obj, obj[:label], obj[:field_type], values) when 'checkboxes' temp2 = cama_form_select_multiple_bootstrap(obj, obj[:label], 'checkbox', values) when 'submit' temp2 = "<button #{cf_attrs(obj[:custom_attrs])} type=\"#{obj[:field_type]}\" name=\"#{f_name}\" " \ "class=\"#{obj[:custom_class].presence || 'btn btn-default'}\">#{obj[:label]}</button>" when 'button' temp2 = "<button #{cf_attrs(obj[:custom_attrs])} type='button' name=\"#{f_name}\" " \ "class=\"#{obj[:custom_class].presence || 'btn btn-default'}\">#{obj[:label]}</button>" when 'reset_button' temp2 = "<button #{cf_attrs(obj[:custom_attrs])} type='reset' name=\"#{f_name}\" " \ "class=\"#{obj[:custom_class].presence || 'btn btn-default'}\">#{obj[:label]}</button>" when 'text', 'website', 'email' class_type = '' class_type = "railscf-field-#{obj[:field_type]}" if obj[:field_type] == 'website' class_type = "railscf-field-#{obj[:field_type]}" if obj[:field_type] == 'email' temp2 = "<input #{cf_attrs(obj[:custom_attrs])} type=\"#{obj[:field_type]}\" value=\"#{current_value}\" " \ "name=\"#{f_name}\" class=\"#{obj[:custom_class].presence || 'form-control'} #{class_type}\">" when 'captcha' if form.recaptcha_enabled? temp2 = 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. captcha_class = "#{obj[:custom_class].presence || 'form-control'} field-captcha required" temp2 = cama_captcha_tag(5, {}, { class: captcha_class }.merge(obj[:custom_attrs])) end when 'file' temp2 = "<input multiple=\"multiple\" type=\"file\" value=\"\" name=\"#{f_name}[]\" " \ "#{cf_attrs(obj[:custom_attrs])} class=\"#{obj[:custom_class].presence || 'form-control'}\">" when 'dropdown' temp2 = cama_form_select_multiple_bootstrap(obj, obj[:label], 'select', values) end r[:template] = cf_substitute(r[:template], '[ci]' => temp2, '[descr ci]' => [:description].to_s.translate, '[label ci]' => for_name).sub('<p></p>', '') html += r[:template] end html end |
#cama_form_select_multiple_bootstrap(obj, title, type, values) ⇒ Object
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 |
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 223 def cama_form_select_multiple_bootstrap(obj, 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. = obj[:field_options][:options] = (.is_a?(Hash) ? .values : Array()) .grep(Hash) include_other_option = obj[:field_options][:include_other_option] other_input = '' f_name = "fields[#{obj[:cid]}]" cid = obj[:cid].to_sym html = '' custom_class = obj[:custom_class].to_s if RADIO_CHECKBOX_CONTROL_TYPES.include?(type) other_input = if include_other_option "<div class=\"#{type} #{custom_class}\"> <label for=\"#{obj[:cid]}\">" \ "<input id=\"#{obj[:cid]}-other\" type=\"#{type}\" name=\"#{title.downcase}[]\" class=\"\">" \ 'Other <input type="text" /></label></div>' else ' ' end else html = "<select #{cf_attrs(obj[:custom_attrs])} name=\"#{f_name}\" class=\"#{custom_class}\">" end .each do |op| label = op[:label].to_s.translate if RADIO_CHECKBOX_CONTROL_TYPES.include?(type) input_tag = "<input #{cf_attrs(obj[:custom_attrs])} type=\"#{type}\" " \ "#{'checked' if op[:checked].to_s.cama_true?} name=\"#{f_name}[]\" " \ "class=\"\" value=\"#{label.downcase}\">" html += "<div class=\"#{type} #{custom_class}\"> <label for=\"#{obj[:cid]}\"> #{input_tag} #{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.tr(' ', '_') html += "<option value=\"#{option_value}\" " \ "#{'selected' if option_value == values[cid] || op[:checked].to_s.cama_true?} >#{label}</option>" end end html += if RADIO_CHECKBOX_CONTROL_TYPES.include?(type) other_input else ' </select>' end end |
#cama_form_shortcode(slug) ⇒ Object
HTML ==================
This returns the format of the plugin shortcode.
78 79 80 |
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 78 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.
102 103 104 |
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 102 def cf_attrs(attrs) (attrs || {}).map { |key, value| "#{key} = \"#{value}\"" }.join(' ') end |
#cf_substitute(template, replacements) ⇒ Object
119 120 121 122 123 124 125 126 127 128 129 |
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 119 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_load ⇒ Object
57 58 59 60 61 62 63 64 65 66 |
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 57 def contact_form_admin_before_load ( '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_load ⇒ Object
68 69 70 71 72 |
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 68 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_load ⇒ Object
74 |
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 74 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
51 |
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 51 def contact_form_on_active(plugin); end |
#contact_form_on_destroy(plugin) ⇒ Object
here all actions on plugin destroying plugin: plugin model
45 |
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 45 def contact_form_on_destroy(plugin); end |
#contact_form_on_export(args) ⇒ Object
14 15 16 |
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 14 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
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 18 def contact_form_on_import(args) plugins = args[:data][:plugins] return if plugins[self_plugin_key.to_sym].blank? plugins[self_plugin_key.to_sym].each do |contact| next if 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)) next unless contact_new.save! save_field_group(contact_new, contact[:get_field_groups]) if contact[:get_field_groups] # save group fields 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 |
#contact_form_on_inactive(plugin) ⇒ Object
here all actions on going to inactive plugin: plugin model
55 |
# File 'app/helpers/plugins/cama_contact_form/main_helper.rb', line 55 def contact_form_on_inactive(plugin); end |