formtastic 1.0.0.beta → 1.0.0.beta2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.textile CHANGED
@@ -517,7 +517,11 @@ If you want to add your own input types to encapsulate your own logic or interfa
517
517
 
518
518
  @Formtastic::SemanticFormHelper.builder = MyCustomBuilder@
519
519
 
520
+ h2. Security
520
521
 
522
+ By default formtastic escapes html entities in both labels and hints unless a string is marked as html_safe. If you are using an older rails version which doesn't know html_safe, or you want to globally turn this feature off, you can set the following in your initializer:
523
+
524
+ Formtastic::SemanticFormBuilder.escape_html_entities_in_hints_and_labels = false
521
525
 
522
526
 
523
527
  h2. Status
data/lib/formtastic.rb CHANGED
@@ -19,11 +19,12 @@ module Formtastic #:nodoc:
19
19
  @@file_methods = [ :file?, :public_filename, :filename ]
20
20
  @@priority_countries = ["Australia", "Canada", "United Kingdom", "United States"]
21
21
  @@i18n_lookups_by_default = false
22
+ @@escape_html_entities_in_hints_and_labels = true
22
23
  @@default_commit_button_accesskey = nil
23
24
 
24
25
  cattr_accessor :default_text_field_size, :default_text_area_height, :all_fields_required_by_default, :include_blank_for_select_by_default,
25
26
  :required_string, :optional_string, :inline_errors, :label_str_method, :collection_label_methods,
26
- :inline_order, :file_methods, :priority_countries, :i18n_lookups_by_default, :default_commit_button_accesskey
27
+ :inline_order, :file_methods, :priority_countries, :i18n_lookups_by_default, :escape_html_entities_in_hints_and_labels, :default_commit_button_accesskey
27
28
 
28
29
  RESERVED_COLUMNS = [:created_at, :updated_at, :created_on, :updated_on, :lock_version, :version]
29
30
 
@@ -478,7 +479,7 @@ module Formtastic #:nodoc:
478
479
  # Collects association columns (relation columns) for the current form object class.
479
480
  #
480
481
  def association_columns(*by_associations) #:nodoc:
481
- if @object.present?
482
+ if @object.present? && @object.class.respond_to?(:reflections)
482
483
  @object.class.reflections.collect do |name, _|
483
484
  if by_associations.present?
484
485
  name if by_associations.include?(_.macro)
@@ -878,7 +879,7 @@ module Formtastic #:nodoc:
878
879
  html_options[:checked] = selected_value == value if selected_option_is_present
879
880
 
880
881
  li_content = template.content_tag(:label,
881
- Formtastic::Util.html_safe("#{self.radio_button(input_name, value, html_options)} #{label}"),
882
+ Formtastic::Util.html_safe("#{self.radio_button(input_name, value, html_options)} #{escape_html_entities(label)}"),
882
883
  :for => input_id
883
884
  )
884
885
 
@@ -888,9 +889,9 @@ module Formtastic #:nodoc:
888
889
 
889
890
  template.content_tag(:fieldset,
890
891
  template.content_tag(:legend,
891
- template.label_tag(nil, localized_string(method, method, :label) || humanized_attribute_name(method), :for => nil), :class => :label
892
+ template.label_tag(nil, localized_string(method, options[:label], :label) || humanized_attribute_name(method), :for => nil), :class => :label
892
893
  ) <<
893
- template.content_tag(:ol, list_item_content)
894
+ template.content_tag(:ol, Formtastic::Util.html_safe(list_item_content.join))
894
895
  )
895
896
  end
896
897
  alias :boolean_radio_input :radio_input
@@ -1149,7 +1150,7 @@ module Formtastic #:nodoc:
1149
1150
  html_options[:id] = input_id
1150
1151
 
1151
1152
  li_content = template.content_tag(:label,
1152
- Formtastic::Util.html_safe("#{self.check_box(input_name, html_options, value, unchecked_value)} #{label}"),
1153
+ Formtastic::Util.html_safe("#{self.check_box(input_name, html_options, value, unchecked_value)} #{escape_html_entities(label)}"),
1153
1154
  :for => input_id
1154
1155
  )
1155
1156
 
@@ -1159,9 +1160,9 @@ module Formtastic #:nodoc:
1159
1160
 
1160
1161
  template.content_tag(:fieldset,
1161
1162
  template.content_tag(:legend,
1162
- template.label_tag(nil, localized_string(method, method, :label) || humanized_attribute_name(method), :for => nil), :class => :label
1163
+ template.label_tag(nil, localized_string(method, options[:label], :label) || humanized_attribute_name(method), :for => nil), :class => :label
1163
1164
  ) <<
1164
- template.content_tag(:ol, list_item_content)
1165
+ template.content_tag(:ol, Formtastic::Util.html_safe(list_item_content.join))
1165
1166
  )
1166
1167
  end
1167
1168
 
@@ -1223,7 +1224,7 @@ module Formtastic #:nodoc:
1223
1224
  #
1224
1225
  def inline_hints_for(method, options) #:nodoc:
1225
1226
  options[:hint] = localized_string(method, options[:hint], :hint)
1226
- return if options[:hint].blank?
1227
+ return if options[:hint].blank? or options[:hint].kind_of? Hash
1227
1228
  template.content_tag(:p, Formtastic::Util.html_safe(options[:hint]), :class => 'inline-hints')
1228
1229
  end
1229
1230
 
@@ -1401,7 +1402,8 @@ module Formtastic #:nodoc:
1401
1402
 
1402
1403
  # Return if we have an Array of strings, fixnums or arrays
1403
1404
  return collection if (collection.instance_of?(Array) || collection.instance_of?(Range)) &&
1404
- [Array, Fixnum, String, Symbol].include?(collection.first.class)
1405
+ [Array, Fixnum, String, Symbol].include?(collection.first.class) &&
1406
+ !(options.include?(:label_method) || options.include?(:value_method))
1405
1407
 
1406
1408
  label, value = detect_label_and_value_method!(collection, options)
1407
1409
  collection.map { |o| [send_or_call(label, o), send_or_call(value, o)] }
@@ -1622,7 +1624,7 @@ module Formtastic #:nodoc:
1622
1624
  key = value if value.is_a?(::Symbol)
1623
1625
 
1624
1626
  if value.is_a?(::String)
1625
- value
1627
+ escape_html_entities(value)
1626
1628
  else
1627
1629
  use_i18n = value.nil? ? @@i18n_lookups_by_default : (value != false)
1628
1630
 
@@ -1644,6 +1646,7 @@ module Formtastic #:nodoc:
1644
1646
 
1645
1647
  i18n_value = ::Formtastic::I18n.t(defaults.shift,
1646
1648
  options.merge(:default => defaults, :scope => type.to_s.pluralize.to_sym))
1649
+ i18n_value = escape_html_entities(i18n_value) if i18n_value.is_a?(::String)
1647
1650
  i18n_value.blank? ? nil : i18n_value
1648
1651
  end
1649
1652
  end
@@ -1676,6 +1679,14 @@ module Formtastic #:nodoc:
1676
1679
  options
1677
1680
  end
1678
1681
 
1682
+ def escape_html_entities(string) #:nodoc:
1683
+ if @@escape_html_entities_in_hints_and_labels
1684
+ # Acceppt html_safe flag as indicator to skip escaping
1685
+ string = template.escape_once(string) unless string.respond_to?(:html_safe?) && string.html_safe? == true
1686
+ end
1687
+ string
1688
+ end
1689
+
1679
1690
  end
1680
1691
 
1681
1692
  # Wrappers around form_for (etc) with :builder => SemanticFormBuilder.
@@ -18,8 +18,11 @@ module Formtastic
18
18
  end
19
19
 
20
20
  def rails_safe_buffer_class
21
- return ActionView::SafeBuffer if defined?(ActionView::SafeBuffer)
22
- ActiveSupport::SafeBuffer
21
+ # It's important that we check ActiveSupport first,
22
+ # because in Rails 2.3.6 ActionView::SafeBuffer exists
23
+ # but is a deprecated proxy object.
24
+ return ActiveSupport::SafeBuffer if defined?(ActiveSupport::SafeBuffer)
25
+ return ActionView::SafeBuffer
23
26
  end
24
27
 
25
28
  end
data/spec/input_spec.rb CHANGED
@@ -571,6 +571,23 @@ describe 'SemanticFormBuilder#input' do
571
571
  end
572
572
  end
573
573
 
574
+ describe 'when localized hint (I18n) is a model with attribute hints' do
575
+ it "should see the provided hash as a blank entry" do
576
+ ::I18n.backend.store_translations :en,
577
+ :formtastic => {
578
+ :hints => {
579
+ :title => { # movie title
580
+ :summary => @localized_hint_text # summary of movie
581
+ }
582
+ }
583
+ }
584
+ semantic_form_for(@new_post) do |builder|
585
+ concat(builder.input(:title, :hint => true))
586
+ end
587
+ output_buffer.should_not have_tag('form li p.inline-hints', @localized_hint_text)
588
+ end
589
+ end
590
+
574
591
  describe 'when localized hint (I18n) is not provided' do
575
592
  it 'should not render a hint paragraph' do
576
593
  semantic_form_for(@new_post) do |builder|
@@ -104,6 +104,16 @@ describe 'check_boxes input' do
104
104
  output_buffer.should have_tag("form li fieldset ol li label input[@name='project[author_id][]']")
105
105
  end
106
106
  end
107
+
108
+ it 'should html escape the label string' do
109
+ output_buffer.replace ''
110
+ semantic_form_for(:project, :url => 'http://test.host') do |builder|
111
+ concat(builder.input(:author_id, :as => :check_boxes, :collection => [["<b>Item 1</b>", 1], ["<b>Item 2</b>", 2]]))
112
+ end
113
+ output_buffer.should have_tag('form li fieldset ol li label') do |label|
114
+ label.body.should match /&lt;b&gt;Item [12]&lt;\/b&gt;$/
115
+ end
116
+ end
107
117
  end
108
118
 
109
119
  describe 'when :selected is set' do
@@ -236,6 +246,7 @@ describe 'check_boxes input' do
236
246
 
237
247
  before do
238
248
  ::I18n.backend.store_translations :en, :formtastic => { :labels => { :post => { :authors => "Translated!" }}}
249
+ Formtastic::SemanticFormBuilder.i18n_lookups_by_default = true
239
250
 
240
251
  @new_post.stub!(:author_ids).and_return(nil)
241
252
  semantic_form_for(@new_post) do |builder|
@@ -245,6 +256,7 @@ describe 'check_boxes input' do
245
256
 
246
257
  after do
247
258
  ::I18n.backend.reload!
259
+ Formtastic::SemanticFormBuilder.i18n_lookups_by_default = false
248
260
  end
249
261
 
250
262
  it "should do foo" do
@@ -252,8 +264,19 @@ describe 'check_boxes input' do
252
264
  end
253
265
 
254
266
  end
255
-
256
- end
257
267
 
268
+ describe "when :label option is set" do
269
+ before do
270
+ @new_post.stub!(:author_ids).and_return(nil)
271
+ semantic_form_for(@new_post) do |builder|
272
+ concat(builder.input(:authors, :as => :check_boxes, :label => 'The authors'))
273
+ end
274
+ end
275
+
276
+ it "should output the correct label title" do
277
+ output_buffer.should have_tag("legend.label label", /The authors/)
278
+ end
279
+ end
280
+ end
258
281
  end
259
282
 
@@ -111,6 +111,16 @@ describe 'radio input' do
111
111
  end
112
112
  end
113
113
 
114
+ it 'should html escape the label string' do
115
+ output_buffer.replace ''
116
+ semantic_form_for(:project, :url => 'http://test.host') do |builder|
117
+ concat(builder.input(:author_id, :as => :radio, :collection => [["<b>Item 1</b>", 1], ["<b>Item 2</b>", 2]]))
118
+ end
119
+ output_buffer.should have_tag('form li fieldset ol li label') do |label|
120
+ label.body.should match /&lt;b&gt;Item [12]&lt;\/b&gt;$/
121
+ end
122
+ end
123
+
114
124
  it 'should generate inputs for each item' do
115
125
  ::Author.find(:all).each do |author|
116
126
  output_buffer.should have_tag("form li fieldset ol li label input#project_author_id_#{author.id}")
@@ -167,6 +177,7 @@ describe 'radio input' do
167
177
  before do
168
178
  ::I18n.backend.store_translations :en, :formtastic => { :labels => { :post => { :authors => "Translated!" }}}
169
179
 
180
+ Formtastic::SemanticFormBuilder.i18n_lookups_by_default = true
170
181
  @new_post.stub!(:author_ids).and_return(nil)
171
182
  semantic_form_for(@new_post) do |builder|
172
183
  concat(builder.input(:authors, :as => :radio))
@@ -175,6 +186,7 @@ describe 'radio input' do
175
186
 
176
187
  after do
177
188
  ::I18n.backend.reload!
189
+ Formtastic::SemanticFormBuilder.i18n_lookups_by_default = false
178
190
  end
179
191
 
180
192
  it "should do foo" do
@@ -183,4 +195,16 @@ describe 'radio input' do
183
195
 
184
196
  end
185
197
 
198
+ describe "when :label option is set" do
199
+ before do
200
+ @new_post.stub!(:author_ids).and_return(nil)
201
+ @form = semantic_form_for(@new_post) do |builder|
202
+ concat(builder.input(:authors, :as => :radio, :label => 'The authors'))
203
+ end
204
+ end
205
+
206
+ it "should output the correct label title" do
207
+ output_buffer.should have_tag("legend.label label", /The authors/)
208
+ end
209
+ end
186
210
  end
data/spec/label_spec.rb CHANGED
@@ -30,6 +30,24 @@ describe 'SemanticFormBuilder#label' do
30
30
  end
31
31
  end
32
32
 
33
+ describe 'when a collection is given' do
34
+ it 'should use a supplied label_method for simple collections' do
35
+ semantic_form_for(:project, :url => 'http://test.host') do |builder|
36
+ concat(builder.input(:author_id, :as => :check_boxes, :collection => [:a, :b, :c], :value_method => :to_s, :label_method => proc {|f| ('Label_%s' % [f])}))
37
+ end
38
+ output_buffer.should have_tag('form li fieldset ol li label', :with => /Label_[abc]/, :count => 3)
39
+ end
40
+
41
+ it 'should use a supplied value_method for simple collections' do
42
+ semantic_form_for(:project, :url => 'http://test.host') do |builder|
43
+ concat(builder.input(:author_id, :as => :check_boxes, :collection => [:a, :b, :c], :value_method => proc {|f| ('Value_%s' % [f.to_s])}))
44
+ end
45
+ output_buffer.should have_tag('form li fieldset ol li label input[value="Value_a"]')
46
+ output_buffer.should have_tag('form li fieldset ol li label input[value="Value_b"]')
47
+ output_buffer.should have_tag('form li fieldset ol li label input[value="Value_c"]')
48
+ end
49
+ end
50
+
33
51
  describe 'when label is given' do
34
52
  it 'should allow the text to be given as label option' do
35
53
  semantic_form_for(@new_post) do |builder|
@@ -42,6 +60,27 @@ describe 'SemanticFormBuilder#label' do
42
60
  builder.label(:login, :label => false).should be_blank
43
61
  end
44
62
  end
63
+
64
+ it 'should html escape the label string by default' do
65
+ semantic_form_for(@new_post) do |builder|
66
+ builder.label(:login, :required => false, :label => '<b>My label</b>').should == "<label for=\"post_login\">&lt;b&gt;My label&lt;/b&gt;</label>"
67
+ end
68
+ end
69
+
70
+ it 'should not html escape the label if configured that way' do
71
+ ::Formtastic::SemanticFormBuilder.escape_html_entities_in_hints_and_labels = false
72
+ semantic_form_for(@new_post) do |builder|
73
+ builder.label(:login, :required => false, :label => '<b>My label</b>').should == "<label for=\"post_login\"><b>My label</b></label>"
74
+ end
75
+ end
76
+
77
+ it 'should not html escape the label string for html_safe strings' do
78
+ ::Formtastic::SemanticFormBuilder.escape_html_entities_in_hints_and_labels = true
79
+ semantic_form_for(@new_post) do |builder|
80
+ builder.label(:login, :required => false, :label => '<b>My label</b>'.html_safe).should == "<label for=\"post_login\"><b>My label</b></label>"
81
+ end
82
+ end
83
+
45
84
  end
46
85
 
47
86
  end
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # coding: utf-8
2
2
  require 'rubygems'
3
3
 
4
+ gem 'i18n', '< 0.4'
5
+
4
6
  gem 'activesupport', '2.3.8'
5
7
  gem 'actionpack', '2.3.8'
6
8
  require 'active_support'
@@ -8,6 +10,8 @@ require 'action_pack'
8
10
  require 'action_view'
9
11
  require 'action_controller'
10
12
 
13
+
14
+
11
15
  gem 'rspec', '>= 1.2.6'
12
16
  gem 'rspec-rails', '>= 1.2.6'
13
17
  gem 'hpricot', '>= 0.6.1'
metadata CHANGED
@@ -6,8 +6,8 @@ version: !ruby/object:Gem::Version
6
6
  - 1
7
7
  - 0
8
8
  - 0
9
- - beta
10
- version: 1.0.0.beta
9
+ - beta2
10
+ version: 1.0.0.beta2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Justin French
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-06-07 00:00:00 +10:00
18
+ date: 2010-07-21 00:00:00 +10:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency