formtastic 1.0.0.beta → 1.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
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