padrino-helpers 0.10.7 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/lib/padrino-helpers.rb +2 -1
  3. data/lib/padrino-helpers/asset_tag_helpers.rb +57 -65
  4. data/lib/padrino-helpers/breadcrumb_helpers.rb +171 -0
  5. data/lib/padrino-helpers/form_builder/abstract_form_builder.rb +82 -24
  6. data/lib/padrino-helpers/form_helpers.rb +84 -17
  7. data/lib/padrino-helpers/format_helpers.rb +2 -1
  8. data/lib/padrino-helpers/locale/fr.yml +12 -12
  9. data/lib/padrino-helpers/locale/pt_br.yml +2 -2
  10. data/lib/padrino-helpers/output_helpers.rb +42 -2
  11. data/lib/padrino-helpers/output_helpers/erb_handler.rb +1 -1
  12. data/lib/padrino-helpers/output_helpers/slim_handler.rb +4 -5
  13. data/lib/padrino-helpers/render_helpers.rb +2 -2
  14. data/lib/padrino-helpers/tag_helpers.rb +33 -5
  15. data/padrino-helpers.gemspec +0 -0
  16. data/test/fixtures/markup_app/app.rb +13 -6
  17. data/test/fixtures/markup_app/views/capture_concat.erb +2 -2
  18. data/test/fixtures/markup_app/views/capture_concat.haml +2 -2
  19. data/test/fixtures/markup_app/views/capture_concat.slim +4 -5
  20. data/test/fixtures/markup_app/views/content_for.slim +4 -4
  21. data/test/fixtures/markup_app/views/content_tag.slim +5 -5
  22. data/test/fixtures/markup_app/views/current_engine.haml +1 -1
  23. data/test/fixtures/markup_app/views/fields_for.slim +13 -13
  24. data/test/fixtures/markup_app/views/form_for.slim +43 -43
  25. data/test/fixtures/markup_app/views/form_tag.slim +57 -57
  26. data/test/fixtures/markup_app/views/link_to.slim +2 -2
  27. data/test/fixtures/markup_app/views/mail_to.slim +2 -2
  28. data/test/fixtures/markup_app/views/meta_tag.slim +2 -2
  29. data/test/fixtures/markup_app/views/partials/_slim.slim +1 -1
  30. data/test/fixtures/markup_app/views/simple_partial.slim +1 -1
  31. data/test/fixtures/render_app/app.rb +3 -0
  32. data/test/test_asset_tag_helpers.rb +17 -4
  33. data/test/test_form_builder.rb +35 -1
  34. data/test/test_form_helpers.rb +29 -0
  35. data/test/test_format_helpers.rb +4 -0
  36. data/test/test_output_helpers.rb +5 -3
  37. data/test/test_tag_helpers.rb +11 -0
  38. metadata +10 -15
@@ -1,3 +1,5 @@
1
+ require 'securerandom'
2
+
1
3
  module Padrino
2
4
  module Helpers
3
5
  ##
@@ -28,8 +30,8 @@ module Padrino
28
30
  #
29
31
  # @api public
30
32
  def form_for(object, url, settings={}, &block)
31
- form_html = capture_html(builder_instance(object, settings), &block)
32
- form_tag(url, settings) { form_html }
33
+ instance = builder_instance(object, settings)
34
+ form_tag(url, settings) { capture_html(instance, &block) }
33
35
  end
34
36
 
35
37
  ##
@@ -54,7 +56,7 @@ module Padrino
54
56
  instance = builder_instance(object, settings)
55
57
  fields_html = capture_html(instance, &block)
56
58
  fields_html << instance.hidden_field(:id) if instance.send(:nested_object_id)
57
- concat_content fields_html
59
+ concat_safe_content fields_html
58
60
  end
59
61
 
60
62
  ##
@@ -79,8 +81,9 @@ module Padrino
79
81
  options.reverse_merge!(:method => 'post', :action => url)
80
82
  options[:enctype] = 'multipart/form-data' if options.delete(:multipart)
81
83
  options['accept-charset'] ||= 'UTF-8'
82
- inner_form_html = hidden_form_method_field(desired_method)
83
- inner_form_html += capture_html(&block)
84
+ inner_form_html = hidden_form_method_field(desired_method)
85
+ inner_form_html << csrf_token_field
86
+ inner_form_html << mark_safe(capture_html(&block))
84
87
  concat_content content_tag(:form, inner_form_html, options)
85
88
  end
86
89
 
@@ -100,7 +103,7 @@ module Padrino
100
103
  #
101
104
  # @api semipublic
102
105
  def hidden_form_method_field(desired_method)
103
- return '' if desired_method.blank? || desired_method.to_s =~ /get|post/i
106
+ return ActiveSupport::SafeBuffer.new if desired_method.blank? || desired_method.to_s =~ /get|post/i
104
107
  hidden_field_tag(:_method, :value => desired_method)
105
108
  end
106
109
 
@@ -125,8 +128,8 @@ module Padrino
125
128
  def field_set_tag(*args, &block)
126
129
  options = args.extract_options!
127
130
  legend_text = args[0].is_a?(String) ? args.first : nil
128
- legend_html = legend_text.blank? ? '' : content_tag(:legend, legend_text)
129
- field_set_content = legend_html + capture_html(&block)
131
+ legend_html = legend_text.blank? ? ActiveSupport::SafeBuffer.new : content_tag(:legend, legend_text)
132
+ field_set_content = legend_html + mark_safe(capture_html(&block))
130
133
  concat_content content_tag(:fieldset, field_set_content, options)
131
134
  end
132
135
 
@@ -199,10 +202,10 @@ module Padrino
199
202
  }
200
203
  }.join
201
204
 
202
- contents = ''
205
+ contents = ActiveSupport::SafeBuffer.new
203
206
  contents << content_tag(options[:header_tag] || :h2, header_message) unless header_message.blank?
204
207
  contents << content_tag(:p, message) unless message.blank?
205
- contents << content_tag(:ul, error_messages)
208
+ contents << safe_content_tag(:ul, error_messages)
206
209
 
207
210
  content_tag(:div, contents, html)
208
211
  end
@@ -276,10 +279,11 @@ module Padrino
276
279
  # @api public
277
280
  def label_tag(name, options={}, &block)
278
281
  options.reverse_merge!(:caption => "#{name.to_s.humanize}: ", :for => name)
279
- caption_text = options.delete(:caption)
280
- caption_text << "<span class='required'>*</span> " if options.delete(:required)
282
+ caption_text = options.delete(:caption).html_safe
283
+ caption_text.safe_concat "<span class='required'>*</span> " if options.delete(:required)
284
+
281
285
  if block_given? # label with inner content
282
- label_content = caption_text + capture_html(&block)
286
+ label_content = caption_text.concat capture_html(&block)
283
287
  concat_content(content_tag(:label, label_content, options))
284
288
  else # regular label
285
289
  content_tag(:label, caption_text, options)
@@ -409,10 +413,10 @@ module Padrino
409
413
  # # => <input name="age" min="18" max="120" step="1" type="number">
410
414
  #
411
415
  # @api public
412
- def number_field_tag(name, options={})
416
+ def number_field_tag(name, options={})
413
417
  input_tag(:number, options.reverse_merge(:name => name))
414
418
  end
415
-
419
+
416
420
  ##
417
421
  # Creates a telephone field input with the given name and options
418
422
  #
@@ -452,7 +456,7 @@ module Padrino
452
456
  def email_field_tag(name, options={})
453
457
  input_tag(:email, options.reverse_merge(:name => name))
454
458
  end
455
-
459
+
456
460
  ##
457
461
  # Creates a search field input with the given name and options
458
462
  #
@@ -573,6 +577,7 @@ module Padrino
573
577
  #
574
578
  # @api public
575
579
  def file_field_tag(name, options={})
580
+ name = "#{name}[]" if options[:multiple]
576
581
  options.reverse_merge!(:name => name)
577
582
  input_tag(:file, options)
578
583
  end
@@ -630,7 +635,7 @@ module Padrino
630
635
  end
631
636
  select_options_html = select_options_html.unshift(blank_option(prompt)) if select_options_html.is_a?(Array)
632
637
  options.merge!(:name => "#{options[:name]}[]") if options[:multiple]
633
- content_tag(:select, select_options_html, options)
638
+ safe_content_tag(:select, select_options_html, options)
634
639
  end
635
640
 
636
641
  ##
@@ -689,6 +694,68 @@ module Padrino
689
694
  input_tag(:image, options)
690
695
  end
691
696
 
697
+ # Constructs a hidden field containing a CSRF token.
698
+ #
699
+ # @param [String] token
700
+ # The token to use. Will be read from the session by default.
701
+ #
702
+ # @return [String] The hidden field with CSRF token as value.
703
+ #
704
+ # @example
705
+ # csrf_token_field
706
+ #
707
+ # @api public
708
+ def csrf_token_field(token = nil)
709
+ if defined? session
710
+ token ||= (session[:csrf] ||= SecureRandom.hex(32))
711
+ end
712
+
713
+ hidden_field_tag :authenticity_token, :value => token
714
+ end
715
+
716
+ ##
717
+ # Creates a form containing a single button that submits to the url.
718
+ #
719
+ # @overload button_to(name, url, options={})
720
+ # @param [String] caption The text caption.
721
+ # @param [String] url The url href.
722
+ # @param [Hash] options The html options.
723
+ # @overload button_to(name, options={}, &block)
724
+ # @param [String] url The url href.
725
+ # @param [Hash] options The html options.
726
+ # @param [Proc] block The button content.
727
+ #
728
+ # @option options [Boolean] :multipart
729
+ # If true, this form will support multipart encoding.
730
+ # @option options [String] :remote
731
+ # Instructs ujs handler to handle the submit as ajax.
732
+ # @option options [Symbol] :method
733
+ # Instructs ujs handler to use different http method (i.e :post, :delete).
734
+ #
735
+ # @return [String] Form and button html with specified +options+.
736
+ #
737
+ # @example
738
+ # button_to 'Delete', url(:accounts_destroy, :id => account), :method => :delete, :class => :form
739
+ # # Generates:
740
+ # # <form class="form" action="/admin/accounts/destroy/2" method="post">
741
+ # # <input type="hidden" value="delete" name="_method" />
742
+ # # <input type="submit" value="Delete" />
743
+ # # </form>
744
+ #
745
+ # @api public
746
+ def button_to(*args, &block)
747
+ name, url = args[0], args[1]
748
+ options = args.extract_options!
749
+ options['data-remote'] = 'true' if options.delete(:remote)
750
+ if block_given?
751
+ form_tag(url, options, &block)
752
+ else
753
+ form_tag(url, options) do
754
+ submit_tag(name)
755
+ end
756
+ end
757
+ end
758
+
692
759
  protected
693
760
 
694
761
  ##
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  module Padrino
2
3
  module Helpers
3
4
  ###
@@ -373,7 +374,7 @@ module Padrino
373
374
  def js_escape_html(html_content)
374
375
  return '' unless html_content
375
376
  javascript_mapping = { '\\' => '\\\\', '</' => '<\/', "\r\n" => '\n', "\n" => '\n', "\r" => '\n', '"' => '\\"', "'" => "\\'" }
376
- html_content.gsub(/(\\|<\/|\r\n|[\n\r"'])/) { javascript_mapping[$1] }
377
+ html_content.gsub(/(\\|<\/|\r\n|[\n\r"'])/){|m| javascript_mapping[m]}
377
378
  end
378
379
  alias :escape_javascript :js_escape_html
379
380
  end # FormatHelpers
@@ -27,25 +27,25 @@ fr:
27
27
  format: "%n %u"
28
28
  units:
29
29
  byte:
30
- one: "Byte"
31
- other: "Bytes"
32
- kb: "KB"
33
- mb: "MB"
34
- gb: "GB"
35
- tb: "TB"
30
+ one: "Octet"
31
+ other: "Octets"
32
+ kb: "Ko"
33
+ mb: "Mo"
34
+ gb: "Go"
35
+ tb: "To"
36
36
 
37
37
  datetime:
38
38
  distance_in_words:
39
39
  half_a_minute: "une demi minute"
40
40
  less_than_x_seconds:
41
- one: "infèrieur à une seconde"
42
- other: "infèrieur à %{count} secondes"
41
+ one: "inférieur à une seconde"
42
+ other: "inférieur à %{count} secondes"
43
43
  x_seconds:
44
44
  one: "1 seconde"
45
45
  other: "%{count} secondes"
46
46
  less_than_x_minutes:
47
- one: "infèrieur à 1 minute"
48
- other: "infèrieur à %{count} minutes"
47
+ one: "inférieur à 1 minute"
48
+ other: "inférieur à %{count} minutes"
49
49
  x_minutes:
50
50
  one: "1 minute"
51
51
  other: "%{count} minutes"
@@ -68,8 +68,8 @@ fr:
68
68
  one: "plus d'un an"
69
69
  other: "plus de %{count} ans"
70
70
  almost_x_years:
71
- one: "près d'un ans"
72
- other: "près de %{count} years"
71
+ one: "près d'un an"
72
+ other: "près de %{count} ans"
73
73
  models:
74
74
  errors:
75
75
  template:
@@ -17,8 +17,8 @@ pt_br:
17
17
  format: "%u%n"
18
18
  unit: "R$"
19
19
  # These three are to override number.format and are optional
20
- separator: "."
21
- delimiter: ","
20
+ separator: ","
21
+ delimiter: "."
22
22
  precision: 2
23
23
 
24
24
  # Used in number_to_percentage()
@@ -26,6 +26,8 @@ module Padrino
26
26
  ##
27
27
  # Captures the html from a block of template code for any available handler.
28
28
  #
29
+ # Be aware that trusting the html is up to the caller.
30
+ #
29
31
  # @param [Object] *args
30
32
  # Objects yield to the captured block
31
33
  # @param [Proc] &block
@@ -37,6 +39,12 @@ module Padrino
37
39
  # capture_html(&block) => "...html..."
38
40
  # capture_html(object_for_block, &block) => "...html..."
39
41
  #
42
+ # @example
43
+ # ActiveSupport::SafeBuffer.new + capture_html { "<foo>" }
44
+ # # => "&lt;foo&gt;"
45
+ # ActiveSupport::SafeBuffer.safe_concat + capture_html { "<foo>" }
46
+ # # => "<foo>"
47
+ #
40
48
  # @api semipublic
41
49
  def capture_html(*args, &block)
42
50
  handler = find_proper_handler
@@ -53,7 +61,9 @@ module Padrino
53
61
  ##
54
62
  # Outputs the given text to the templates buffer directly.
55
63
  #
56
- # @param [String] text
64
+ # The output might be subject to escaping, if it is not marked as safe.
65
+ #
66
+ # @param [String,SafeBuffer] text
57
67
  # Text to concatenate to the buffer.
58
68
  #
59
69
  # @example
@@ -70,6 +80,21 @@ module Padrino
70
80
  end
71
81
  alias :concat :concat_content
72
82
 
83
+ ##
84
+ # Outputs the given text to the templates buffer directly,
85
+ # assuming that it is safe.
86
+ #
87
+ # @param [String] text
88
+ # Text to concatenate to the buffer.
89
+ #
90
+ # @example
91
+ # concat_safe_content("This will be output to the template buffer")
92
+ #
93
+ # @api semipublic
94
+ def concat_safe_content(text="")
95
+ concat_content text.html_safe
96
+ end
97
+
73
98
  ##
74
99
  # Returns true if the block is from a supported template type; false otherwise.
75
100
  # Used to determine if html should be returned or concatenated to the view.
@@ -146,7 +171,7 @@ module Padrino
146
171
  def yield_content(key, *args)
147
172
  blocks = content_blocks[key.to_sym]
148
173
  return nil if blocks.empty?
149
- blocks.map { |content| capture_html(*args, &content) }.join
174
+ mark_safe(blocks.map { |content| capture_html(*args, &content) }.join)
150
175
  end
151
176
 
152
177
  protected
@@ -170,6 +195,21 @@ module Padrino
170
195
  def find_proper_handler
171
196
  OutputHelpers.handlers.map { |h| h.new(self) }.find { |h| h.engines.include?(current_engine) && h.is_type? }
172
197
  end
198
+
199
+ ##
200
+ # Marks a String or a collection of Strings as safe. `nil` is accepted
201
+ # but ignored.
202
+ #
203
+ # @param [String, Array<String>] the values to be marked safe.
204
+ #
205
+ # @return [ActiveSupport::SafeBuffer, Array<ActiveSupport::SafeBuffer>]
206
+ def mark_safe(value)
207
+ if value.respond_to? :map!
208
+ value.map!{|v| v.html_safe if v }
209
+ else
210
+ value.html_safe if value
211
+ end
212
+ end
173
213
  end # OutputHelpers
174
214
  end # Helpers
175
215
  end # Padrino
@@ -28,7 +28,7 @@ module Padrino
28
28
  # @handler.capture_from_template(&block) => "...html..."
29
29
  #
30
30
  def capture_from_template(*args, &block)
31
- self.output_buffer, _buf_was = "", self.output_buffer
31
+ self.output_buffer, _buf_was = ActiveSupport::SafeBuffer.new, self.output_buffer
32
32
  block.call(*args)
33
33
  ret = eval("@_out_buf", block.binding)
34
34
  self.output_buffer = _buf_was
@@ -1,5 +1,4 @@
1
1
  # Make slim works with sinatra/padrino
2
- Slim::Engine.set_default_options(:buffer => '@_out_buf', :generator => Temple::Generators::StringBuffer) if defined?(Slim)
3
2
 
4
3
  module Padrino
5
4
  module Helpers
@@ -31,9 +30,9 @@ module Padrino
31
30
  # @handler.capture_from_template(&block) => "...html..."
32
31
  #
33
32
  def capture_from_template(*args, &block)
34
- self.output_buffer, _buf_was = '', self.output_buffer
33
+ self.output_buffer, _buf_was = ActiveSupport::SafeBuffer.new, self.output_buffer
35
34
  block.call(*args)
36
- ret = eval('@_out_buf', block.binding)
35
+ ret = eval("@_out_buf", block.binding)
37
36
  self.output_buffer = _buf_was
38
37
  ret
39
38
  end
@@ -73,9 +72,9 @@ module Padrino
73
72
  def output_buffer=(val)
74
73
  template.instance_variable_set(:@_out_buf, val)
75
74
  end
76
- end # SlimHandler
75
+ end # SlimHandler
77
76
 
78
- OutputHelpers.register(SlimHandler)
77
+ OutputHelpers.register(SlimHandler)
79
78
  end # OutputHelpers
80
79
  end # Helpers
81
80
  end # Padrino
@@ -46,12 +46,12 @@ module Padrino
46
46
  counter += 1
47
47
  options[:locals].merge!(object_name => member, "#{object_name}_counter".to_sym => counter)
48
48
  render(explicit_engine, template_path, options.dup)
49
- }.join("\n")
49
+ }.join("\n").html_safe
50
50
  else
51
51
  if member = options.delete(:object)
52
52
  options[:locals].merge!(object_name => member)
53
53
  end
54
- render(explicit_engine, template_path, options.dup)
54
+ render(explicit_engine, template_path, options.dup).html_safe
55
55
  end
56
56
  end
57
57
  alias :render_partial :partial
@@ -13,6 +13,12 @@ module Padrino
13
13
  '"' => "&quot;"
14
14
  }
15
15
 
16
+ ##
17
+ # Cached Regexp for escaping values to avoid rebuilding one
18
+ # on every escape operation.
19
+ #
20
+ ESCAPE_REGEXP = Regexp.union(*ESCAPE_VALUES.keys)
21
+
16
22
  BOOLEAN_ATTRIBUTES = [
17
23
  :autoplay,
18
24
  :autofocus,
@@ -42,6 +48,12 @@ module Padrino
42
48
  :confirm
43
49
  ]
44
50
 
51
+ ##
52
+ # A html_safe newline string to avoid allocating a new on each
53
+ # concatenation.
54
+ #
55
+ NEWLINE = "\n".html_safe.freeze
56
+
45
57
  ##
46
58
  # Creates an HTML tag with given name, content, and options
47
59
  #
@@ -104,14 +116,30 @@ module Padrino
104
116
  content = capture_html(&block)
105
117
  end
106
118
 
107
- content = content.join("\n") if content.respond_to?(:join)
108
-
109
119
  options = parse_data_options(name, options)
110
120
  attributes = tag_attributes(options)
111
- output = "<#{name}#{attributes}>#{content}</#{name}>"
121
+ output = ActiveSupport::SafeBuffer.new
122
+ output.safe_concat "<#{name}#{attributes}>"
123
+ if content.respond_to?(:each) && !content.is_a?(String)
124
+ content.each { |c| output.concat c; output.safe_concat NEWLINE }
125
+ else
126
+ output.concat content
127
+ end
128
+ output.safe_concat "</#{name}>"
129
+
112
130
  block_is_template?(block) ? concat_content(output) : output
113
131
  end
114
132
 
133
+ ##
134
+ # Like #content_tag, but assumes its input to be safe and doesn't
135
+ # escape. It also returns safe html.
136
+ #
137
+ # @see #content_tag
138
+ #
139
+ def safe_content_tag(name, content = nil, options = nil, &block)
140
+ mark_safe(content_tag(name, mark_safe(content), options, &block))
141
+ end
142
+
115
143
  ##
116
144
  # Creates an HTML input field with the given type and options
117
145
  #
@@ -200,7 +228,7 @@ module Padrino
200
228
  def tag(name, options = nil, open = false)
201
229
  options = parse_data_options(name, options)
202
230
  attributes = tag_attributes(options)
203
- "<#{name}#{attributes}#{open ? '>' : ' />'}"
231
+ "<#{name}#{attributes}#{open ? '>' : ' />'}".html_safe
204
232
  end
205
233
 
206
234
  private
@@ -226,7 +254,7 @@ module Padrino
226
254
  # Escape tag values to their HTML/XML entities.
227
255
  ##
228
256
  def escape_value(string)
229
- string.to_s.gsub(Regexp.union(*ESCAPE_VALUES.keys)) { |c| ESCAPE_VALUES[c] }
257
+ string.to_s.gsub(ESCAPE_REGEXP) { |c| ESCAPE_VALUES[c] }
230
258
  end
231
259
 
232
260
  ##