actionview 7.0.1 → 7.1.1

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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +281 -202
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +3 -3
  5. data/app/assets/javascripts/rails-ujs.esm.js +693 -0
  6. data/app/assets/javascripts/rails-ujs.js +630 -0
  7. data/lib/action_view/base.rb +33 -12
  8. data/lib/action_view/buffers.rb +106 -8
  9. data/lib/action_view/cache_expiry.rb +40 -43
  10. data/lib/action_view/context.rb +1 -1
  11. data/lib/action_view/deprecator.rb +7 -0
  12. data/lib/action_view/digestor.rb +1 -1
  13. data/lib/action_view/gem_version.rb +2 -2
  14. data/lib/action_view/helpers/active_model_helper.rb +1 -1
  15. data/lib/action_view/helpers/asset_tag_helper.rb +133 -48
  16. data/lib/action_view/helpers/asset_url_helper.rb +13 -12
  17. data/lib/action_view/helpers/atom_feed_helper.rb +5 -5
  18. data/lib/action_view/helpers/cache_helper.rb +3 -9
  19. data/lib/action_view/helpers/capture_helper.rb +26 -12
  20. data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
  21. data/lib/action_view/helpers/controller_helper.rb +6 -0
  22. data/lib/action_view/helpers/csp_helper.rb +2 -2
  23. data/lib/action_view/helpers/csrf_helper.rb +3 -3
  24. data/lib/action_view/helpers/date_helper.rb +76 -64
  25. data/lib/action_view/helpers/debug_helper.rb +3 -3
  26. data/lib/action_view/helpers/form_helper.rb +62 -31
  27. data/lib/action_view/helpers/form_options_helper.rb +6 -3
  28. data/lib/action_view/helpers/form_tag_helper.rb +88 -44
  29. data/lib/action_view/helpers/javascript_helper.rb +1 -0
  30. data/lib/action_view/helpers/number_helper.rb +15 -13
  31. data/lib/action_view/helpers/output_safety_helper.rb +4 -4
  32. data/lib/action_view/helpers/rendering_helper.rb +5 -6
  33. data/lib/action_view/helpers/sanitize_helper.rb +34 -15
  34. data/lib/action_view/helpers/tag_helper.rb +27 -16
  35. data/lib/action_view/helpers/tags/base.rb +11 -52
  36. data/lib/action_view/helpers/tags/collection_check_boxes.rb +1 -0
  37. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -0
  38. data/lib/action_view/helpers/tags/collection_select.rb +3 -0
  39. data/lib/action_view/helpers/tags/date_field.rb +1 -1
  40. data/lib/action_view/helpers/tags/date_select.rb +2 -0
  41. data/lib/action_view/helpers/tags/datetime_field.rb +14 -6
  42. data/lib/action_view/helpers/tags/datetime_local_field.rb +11 -2
  43. data/lib/action_view/helpers/tags/grouped_collection_select.rb +3 -0
  44. data/lib/action_view/helpers/tags/month_field.rb +1 -1
  45. data/lib/action_view/helpers/tags/select.rb +4 -1
  46. data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
  47. data/lib/action_view/helpers/tags/time_field.rb +1 -1
  48. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -0
  49. data/lib/action_view/helpers/tags/week_field.rb +1 -1
  50. data/lib/action_view/helpers/tags/weekday_select.rb +3 -0
  51. data/lib/action_view/helpers/tags.rb +2 -0
  52. data/lib/action_view/helpers/text_helper.rb +33 -17
  53. data/lib/action_view/helpers/translation_helper.rb +6 -6
  54. data/lib/action_view/helpers/url_helper.rb +90 -65
  55. data/lib/action_view/helpers.rb +2 -0
  56. data/lib/action_view/layouts.rb +13 -8
  57. data/lib/action_view/log_subscriber.rb +49 -32
  58. data/lib/action_view/lookup_context.rb +29 -13
  59. data/lib/action_view/path_registry.rb +57 -0
  60. data/lib/action_view/path_set.rb +13 -14
  61. data/lib/action_view/railtie.rb +26 -3
  62. data/lib/action_view/record_identifier.rb +16 -9
  63. data/lib/action_view/renderer/abstract_renderer.rb +1 -1
  64. data/lib/action_view/renderer/collection_renderer.rb +9 -1
  65. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +21 -3
  66. data/lib/action_view/renderer/partial_renderer.rb +3 -2
  67. data/lib/action_view/renderer/renderer.rb +2 -0
  68. data/lib/action_view/renderer/streaming_template_renderer.rb +3 -2
  69. data/lib/action_view/renderer/template_renderer.rb +3 -2
  70. data/lib/action_view/rendering.rb +24 -6
  71. data/lib/action_view/ripper_ast_parser.rb +6 -6
  72. data/lib/action_view/routing_url_for.rb +7 -4
  73. data/lib/action_view/template/error.rb +14 -1
  74. data/lib/action_view/template/handlers/builder.rb +4 -4
  75. data/lib/action_view/template/handlers/erb/erubi.rb +23 -27
  76. data/lib/action_view/template/handlers/erb.rb +73 -1
  77. data/lib/action_view/template/handlers.rb +1 -1
  78. data/lib/action_view/template/html.rb +1 -1
  79. data/lib/action_view/template/raw_file.rb +1 -1
  80. data/lib/action_view/template/renderable.rb +1 -1
  81. data/lib/action_view/template/resolver.rb +15 -5
  82. data/lib/action_view/template/text.rb +1 -1
  83. data/lib/action_view/template/types.rb +25 -34
  84. data/lib/action_view/template.rb +227 -53
  85. data/lib/action_view/template_path.rb +2 -0
  86. data/lib/action_view/test_case.rb +174 -21
  87. data/lib/action_view/unbound_template.rb +15 -5
  88. data/lib/action_view/version.rb +1 -1
  89. data/lib/action_view/view_paths.rb +19 -28
  90. data/lib/action_view.rb +4 -1
  91. data/lib/assets/compiled/rails-ujs.js +36 -5
  92. metadata +27 -27
@@ -5,17 +5,18 @@ require "active_support/core_ext/string/output_safety"
5
5
  require "active_support/number_helper"
6
6
 
7
7
  module ActionView
8
- # = Action View Number Helpers
9
8
  module Helpers # :nodoc:
9
+ # = Action View Number \Helpers
10
+ #
10
11
  # Provides methods for converting numbers into formatted strings.
11
12
  # Methods are provided for phone numbers, currency, percentage,
12
- # precision, positional notation, file size and pretty printing.
13
+ # precision, positional notation, file size, and pretty printing.
13
14
  #
14
15
  # Most methods expect a +number+ argument, and will return it
15
16
  # unchanged if can't be converted into a valid number.
16
17
  module NumberHelper
17
18
  # Raised when argument +number+ param given to the helpers is invalid and
18
- # the option :raise is set to +true+.
19
+ # the option +:raise+ is set to +true+.
19
20
  class InvalidNumberError < StandardError
20
21
  attr_accessor :number
21
22
  def initialize(number)
@@ -202,7 +203,7 @@ module ActionView
202
203
  # number_with_delimiter("123456.78",
203
204
  # delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/) # => "1,23,456.78"
204
205
  #
205
- # number_with_delimiter("112a", raise: true) # => raise InvalidNumberError
206
+ # number_with_delimiter("112a", raise: true) # => raise InvalidNumberError
206
207
  def number_with_delimiter(number, options = {})
207
208
  delegate_number_helper_method(:number_to_delimited, number, options)
208
209
  end
@@ -370,13 +371,14 @@ module ActionView
370
371
  # out by default (set <tt>:strip_insignificant_zeros</tt> to
371
372
  # +false+ to change that):
372
373
  #
373
- # number_to_human(12.00001) # => "12"
374
- # number_to_human(12.00001, strip_insignificant_zeros: false) # => "12.0"
374
+ # number_to_human(12.00001) # => "12"
375
+ # number_to_human(12.00001, strip_insignificant_zeros: false) # => "12.0"
375
376
  #
376
377
  # ==== Custom Unit Quantifiers
377
378
  #
378
379
  # You can also use your own custom unit quantifiers:
379
- # number_to_human(500000, units: {unit: "ml", thousand: "lt"}) # => "500 lt"
380
+ #
381
+ # number_to_human(500000, units: {unit: "ml", thousand: "lt"}) # => "500 lt"
380
382
  #
381
383
  # If in your I18n locale you have:
382
384
  # distance:
@@ -393,12 +395,12 @@ module ActionView
393
395
  #
394
396
  # Then you could do:
395
397
  #
396
- # number_to_human(543934, units: :distance) # => "544 kilometers"
397
- # number_to_human(54393498, units: :distance) # => "54400 kilometers"
398
- # number_to_human(54393498000, units: :distance) # => "54.4 gazillion-distance"
399
- # number_to_human(343, units: :distance, precision: 1) # => "300 meters"
400
- # number_to_human(1, units: :distance) # => "1 meter"
401
- # number_to_human(0.34, units: :distance) # => "34 centimeters"
398
+ # number_to_human(543934, units: :distance) # => "544 kilometers"
399
+ # number_to_human(54393498, units: :distance) # => "54400 kilometers"
400
+ # number_to_human(54393498000, units: :distance) # => "54.4 gazillion-distance"
401
+ # number_to_human(343, units: :distance, precision: 1) # => "300 meters"
402
+ # number_to_human(1, units: :distance) # => "1 meter"
403
+ # number_to_human(0.34, units: :distance) # => "34 centimeters"
402
404
  #
403
405
  def number_to_human(number, options = {})
404
406
  delegate_number_helper_method(:number_to_human, number, options)
@@ -3,18 +3,18 @@
3
3
  require "active_support/core_ext/string/output_safety"
4
4
 
5
5
  module ActionView # :nodoc:
6
- # = Action View Raw Output Helper
7
6
  module Helpers # :nodoc:
7
+ # = Action View Raw Output \Helpers
8
8
  module OutputSafetyHelper
9
9
  # This method outputs without escaping a string. Since escaping tags is
10
- # now default, this can be used when you don't want Rails to automatically
10
+ # now default, this can be used when you don't want \Rails to automatically
11
11
  # escape tags. This is not recommended if the data is coming from the user's
12
12
  # input.
13
13
  #
14
14
  # For example:
15
15
  #
16
- # raw @user.name
17
- # # => 'Jimmy <alert>Tables</alert>'
16
+ # raw @user.name
17
+ # # => 'Jimmy <alert>Tables</alert>'
18
18
  def raw(stringish)
19
19
  stringish.to_s.html_safe
20
20
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module ActionView
4
4
  module Helpers # :nodoc:
5
- # = Action View Rendering
5
+ # = Action View \Rendering \Helpers
6
6
  #
7
7
  # Implements methods that allow rendering from a view context.
8
8
  # In order to use this module, all you need is to implement
@@ -10,8 +10,8 @@ module ActionView
10
10
  module RenderingHelper
11
11
  # Returns the result of a render that's dictated by the options hash. The primary options are:
12
12
  #
13
- # * <tt>:partial</tt> - See <tt>ActionView::PartialRenderer</tt>.
14
- # * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
13
+ # * <tt>:partial</tt> - See ActionView::PartialRenderer.
14
+ # * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add +:locals+ to pass in those.
15
15
  # * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller.
16
16
  # * <tt>:plain</tt> - Renders the text passed in out. Setting the content
17
17
  # type as <tt>text/plain</tt>.
@@ -19,8 +19,7 @@ module ActionView
19
19
  # performs HTML escape on the string first. Setting the content type as
20
20
  # <tt>text/html</tt>.
21
21
  # * <tt>:body</tt> - Renders the text passed in, and inherits the content
22
- # type of <tt>text/plain</tt> from <tt>ActionDispatch::Response</tt>
23
- # object.
22
+ # type of <tt>text/plain</tt> from ActionDispatch::Response object.
24
23
  #
25
24
  # If no <tt>options</tt> hash is passed or if <tt>:update</tt> is specified, then:
26
25
  #
@@ -47,7 +46,7 @@ module ActionView
47
46
  end
48
47
  end
49
48
 
50
- # Overwrites _layout_for in the context object so it supports the case a block is
49
+ # Overrides _layout_for in the context object so it supports the case a block is
51
50
  # passed to a partial. Returns the contents that are yielded to a layout, given a
52
51
  # name or a block.
53
52
  #
@@ -3,20 +3,23 @@
3
3
  require "rails-html-sanitizer"
4
4
 
5
5
  module ActionView
6
- # = Action View Sanitize Helpers
7
6
  module Helpers # :nodoc:
7
+ # = Action View Sanitize \Helpers
8
+ #
8
9
  # The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements.
9
10
  # These helper methods extend Action View making them callable within your template files.
10
11
  module SanitizeHelper
12
+ mattr_accessor :sanitizer_vendor, default: Rails::HTML4::Sanitizer
13
+
11
14
  extend ActiveSupport::Concern
15
+
12
16
  # Sanitizes HTML input, stripping all but known-safe tags and attributes.
13
17
  #
14
- # It also strips href/src attributes with unsafe protocols like
15
- # <tt>javascript:</tt>, while also protecting against attempts to use Unicode,
16
- # ASCII, and hex character references to work around these protocol filters.
17
- # All special characters will be escaped.
18
+ # It also strips href/src attributes with unsafe protocols like <tt>javascript:</tt>, while
19
+ # also protecting against attempts to use Unicode, ASCII, and hex character references to work
20
+ # around these protocol filters.
18
21
  #
19
- # The default sanitizer is Rails::Html::SafeListSanitizer. See {Rails HTML
22
+ # The default sanitizer is Rails::HTML5::SafeListSanitizer. See {Rails HTML
20
23
  # Sanitizers}[https://github.com/rails/rails-html-sanitizer] for more information.
21
24
  #
22
25
  # Custom sanitization rules can also be provided.
@@ -28,7 +31,7 @@ module ActionView
28
31
  #
29
32
  # * <tt>:tags</tt> - An array of allowed tags.
30
33
  # * <tt>:attributes</tt> - An array of allowed attributes.
31
- # * <tt>:scrubber</tt> - A {Rails::Html scrubber}[https://github.com/rails/rails-html-sanitizer]
34
+ # * <tt>:scrubber</tt> - A {Rails::HTML scrubber}[https://github.com/rails/rails-html-sanitizer]
32
35
  # or {Loofah::Scrubber}[https://github.com/flavorjones/loofah] object that
33
36
  # defines custom sanitization rules. A custom scrubber takes precedence over
34
37
  # custom tags and attributes.
@@ -43,9 +46,9 @@ module ActionView
43
46
  #
44
47
  # <%= sanitize @comment.body, tags: %w(strong em a), attributes: %w(href) %>
45
48
  #
46
- # Providing a custom Rails::Html scrubber:
49
+ # Providing a custom Rails::HTML scrubber:
47
50
  #
48
- # class CommentScrubber < Rails::Html::PermitScrubber
51
+ # class CommentScrubber < Rails::HTML::PermitScrubber
49
52
  # def initialize
50
53
  # super
51
54
  # self.tags = %w( form script comment blockquote )
@@ -60,7 +63,7 @@ module ActionView
60
63
  # <%= sanitize @comment.body, scrubber: CommentScrubber.new %>
61
64
  #
62
65
  # See {Rails HTML Sanitizer}[https://github.com/rails/rails-html-sanitizer] for
63
- # documentation about Rails::Html scrubbers.
66
+ # documentation about Rails::HTML scrubbers.
64
67
  #
65
68
  # Providing a custom Loofah::Scrubber:
66
69
  #
@@ -78,6 +81,22 @@ module ActionView
78
81
  # # In config/application.rb
79
82
  # config.action_view.sanitized_allowed_tags = ['strong', 'em', 'a']
80
83
  # config.action_view.sanitized_allowed_attributes = ['href', 'title']
84
+ #
85
+ # The default, starting in \Rails 7.1, is to use an HTML5 parser for sanitization (if it is
86
+ # available, see NOTE below). If you wish to revert back to the previous HTML4 behavior, you
87
+ # can do so by setting the following in your application configuration:
88
+ #
89
+ # # In config/application.rb
90
+ # config.action_view.sanitizer_vendor = Rails::HTML4::Sanitizer
91
+ #
92
+ # Or, if you're upgrading from a previous version of \Rails and wish to opt into the HTML5
93
+ # behavior:
94
+ #
95
+ # # In config/application.rb
96
+ # config.action_view.sanitizer_vendor = Rails::HTML5::Sanitizer
97
+ #
98
+ # NOTE: Rails::HTML5::Sanitizer is not supported on JRuby, so on JRuby platforms \Rails will
99
+ # fall back to use Rails::HTML4::Sanitizer.
81
100
  def sanitize(html, options = {})
82
101
  self.class.safe_list_sanitizer.sanitize(html, options)&.html_safe
83
102
  end
@@ -101,7 +120,7 @@ module ActionView
101
120
  # strip_tags("> A quote from Smith & Wesson")
102
121
  # # => &gt; A quote from Smith &amp; Wesson
103
122
  def strip_tags(html)
104
- self.class.full_sanitizer.sanitize(html)
123
+ self.class.full_sanitizer.sanitize(html)&.html_safe
105
124
  end
106
125
 
107
126
  # Strips all link tags from +html+ leaving just the link text.
@@ -125,7 +144,7 @@ module ActionView
125
144
  attr_writer :full_sanitizer, :link_sanitizer, :safe_list_sanitizer
126
145
 
127
146
  def sanitizer_vendor
128
- Rails::Html::Sanitizer
147
+ ActionView::Helpers::SanitizeHelper.sanitizer_vendor
129
148
  end
130
149
 
131
150
  def sanitized_allowed_tags
@@ -136,7 +155,7 @@ module ActionView
136
155
  sanitizer_vendor.safe_list_sanitizer.allowed_attributes
137
156
  end
138
157
 
139
- # Gets the Rails::Html::FullSanitizer instance used by +strip_tags+. Replace with
158
+ # Gets the Rails::HTML::FullSanitizer instance used by +strip_tags+. Replace with
140
159
  # any object that responds to +sanitize+.
141
160
  #
142
161
  # class Application < Rails::Application
@@ -146,7 +165,7 @@ module ActionView
146
165
  @full_sanitizer ||= sanitizer_vendor.full_sanitizer.new
147
166
  end
148
167
 
149
- # Gets the Rails::Html::LinkSanitizer instance used by +strip_links+.
168
+ # Gets the Rails::HTML::LinkSanitizer instance used by +strip_links+.
150
169
  # Replace with any object that responds to +sanitize+.
151
170
  #
152
171
  # class Application < Rails::Application
@@ -156,7 +175,7 @@ module ActionView
156
175
  @link_sanitizer ||= sanitizer_vendor.link_sanitizer.new
157
176
  end
158
177
 
159
- # Gets the Rails::Html::SafeListSanitizer instance used by sanitize and +sanitize_css+.
178
+ # Gets the Rails::HTML::SafeListSanitizer instance used by sanitize and +sanitize_css+.
160
179
  # Replace with any object that responds to +sanitize+.
161
180
  #
162
181
  # class Application < Rails::Application
@@ -7,8 +7,9 @@ require "action_view/helpers/capture_helper"
7
7
  require "action_view/helpers/output_safety_helper"
8
8
 
9
9
  module ActionView
10
- # = Action View Tag Helpers
11
10
  module Helpers # :nodoc:
11
+ # = Action View Tag \Helpers
12
+ #
12
13
  # Provides methods to generate HTML tags programmatically both as a modern
13
14
  # HTML5 compliant builder style and legacy XHTML compliant tags.
14
15
  module TagHelper
@@ -45,8 +46,8 @@ module ActionView
45
46
  include CaptureHelper
46
47
  include OutputSafetyHelper
47
48
 
48
- HTML_VOID_ELEMENTS = %i(area base br col circle embed hr img input keygen link meta param source track wbr).to_set
49
- SVG_VOID_ELEMENTS = %i(animate animateMotion animateTransform circle ellipse line path polygon polyline rect set stop use view).to_set
49
+ HTML_VOID_ELEMENTS = %i(area base br col embed hr img input keygen link meta param source track wbr).to_set
50
+ SVG_SELF_CLOSING_ELEMENTS = %i(animate animateMotion animateTransform circle ellipse line path polygon polyline rect set stop use view).to_set
50
51
 
51
52
  def initialize(view_context)
52
53
  @view_context = view_context
@@ -65,18 +66,24 @@ module ActionView
65
66
  tag_string(:p, *arguments, **options, &block)
66
67
  end
67
68
 
68
- def tag_string(name, content = nil, escape_attributes: true, **options, &block)
69
+ def tag_string(name, content = nil, escape: true, **options, &block)
69
70
  content = @view_context.capture(self, &block) if block_given?
70
- if (HTML_VOID_ELEMENTS.include?(name) || SVG_VOID_ELEMENTS.include?(name)) && content.nil?
71
- "<#{name.to_s.dasherize}#{tag_options(options, escape_attributes)}>".html_safe
71
+ self_closing = SVG_SELF_CLOSING_ELEMENTS.include?(name)
72
+ if (HTML_VOID_ELEMENTS.include?(name) || self_closing) && content.nil?
73
+ "<#{name.to_s.dasherize}#{tag_options(options, escape)}#{self_closing ? " />" : ">"}".html_safe
72
74
  else
73
- content_tag_string(name.to_s.dasherize, content || "", options, escape_attributes)
75
+ content_tag_string(name.to_s.dasherize, content || "", options, escape)
74
76
  end
75
77
  end
76
78
 
77
79
  def content_tag_string(name, content, options, escape = true)
78
80
  tag_options = tag_options(options, escape) if options
79
- content = ERB::Util.unwrapped_html_escape(content) if escape
81
+
82
+ if escape
83
+ name = ERB::Util.xml_name_escape(name)
84
+ content = ERB::Util.unwrapped_html_escape(content)
85
+ end
86
+
80
87
  "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe
81
88
  end
82
89
 
@@ -127,6 +134,8 @@ module ActionView
127
134
  end
128
135
 
129
136
  def tag_option(key, value, escape)
137
+ key = ERB::Util.xml_name_escape(key) if escape
138
+
130
139
  case value
131
140
  when Array, Hash
132
141
  value = TagHelper.build_tag_values(value) if key.to_s == "class"
@@ -137,6 +146,7 @@ module ActionView
137
146
  value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
138
147
  end
139
148
  value = value.gsub('"', "&quot;") if value.include?('"')
149
+
140
150
  %(#{key}="#{value}")
141
151
  end
142
152
 
@@ -208,7 +218,7 @@ module ActionView
208
218
  #
209
219
  # Thus <tt>data-user-id</tt> can be accessed as <tt>dataset.userId</tt>.
210
220
  #
211
- # Data attribute values are encoded to JSON, with the exception of strings, symbols and
221
+ # Data attribute values are encoded to JSON, with the exception of strings, symbols, and
212
222
  # BigDecimals.
213
223
  # This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt>
214
224
  # from 1.4.3.
@@ -216,13 +226,13 @@ module ActionView
216
226
  # tag.div data: { city_state: %w( Chicago IL ) }
217
227
  # # => <div data-city-state="[&quot;Chicago&quot;,&quot;IL&quot;]"></div>
218
228
  #
219
- # The generated attributes are escaped by default. This can be disabled using
220
- # +escape_attributes+.
229
+ # The generated tag names and attributes are escaped by default. This can be disabled using
230
+ # +escape+.
221
231
  #
222
232
  # tag.img src: 'open & shut.png'
223
233
  # # => <img src="open &amp; shut.png">
224
234
  #
225
- # tag.img src: 'open & shut.png', escape_attributes: false
235
+ # tag.img src: 'open & shut.png', escape: false
226
236
  # # => <img src="open & shut.png">
227
237
  #
228
238
  # The tag builder respects
@@ -240,7 +250,7 @@ module ActionView
240
250
  # Transforms a Hash into HTML attributes, ready to be interpolated into
241
251
  # ERB. Includes or omits boolean attributes based on their truthiness.
242
252
  # Transforms keys nested within
243
- # <tt>aria:</tt> or <tt>data:</tt> objects into `aria-` and `data-`
253
+ # <tt>aria:</tt> or <tt>data:</tt> objects into <tt>aria-</tt> and <tt>data-</tt>
244
254
  # prefixed attributes:
245
255
  #
246
256
  # <input <%= tag.attributes(type: :text, aria: { label: "Search" }) %>>
@@ -251,7 +261,7 @@ module ActionView
251
261
  #
252
262
  # === Legacy syntax
253
263
  #
254
- # The following format is for legacy syntax support. It will be deprecated in future versions of Rails.
264
+ # The following format is for legacy syntax support. It will be deprecated in future versions of \Rails.
255
265
  #
256
266
  # tag(name, options = nil, open = false, escape = true)
257
267
  #
@@ -300,6 +310,7 @@ module ActionView
300
310
  if name.nil?
301
311
  tag_builder
302
312
  else
313
+ name = ERB::Util.xml_name_escape(name) if escape
303
314
  "<#{name}#{tag_builder.tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
304
315
  end
305
316
  end
@@ -308,7 +319,7 @@ module ActionView
308
319
  # HTML attributes by passing an attributes hash to +options+.
309
320
  # Instead of passing the content as an argument, you can also use a block
310
321
  # in which case, you pass your +options+ as the second parameter.
311
- # Set escape to false to disable attribute value escaping.
322
+ # Set escape to false to disable escaping.
312
323
  # Note: this is legacy syntax, see +tag+ method description for details.
313
324
  #
314
325
  # ==== Options
@@ -353,7 +364,7 @@ module ActionView
353
364
  # token_list(nil, false, 123, "", "foo", { bar: true })
354
365
  # # => "123 foo bar"
355
366
  def token_list(*args)
356
- tokens = build_tag_values(*args).flat_map { |value| value.to_s.split(/\s+/) }.uniq
367
+ tokens = build_tag_values(*args).flat_map { |value| CGI.unescape_html(value.to_s).split(/\s+/) }.uniq
357
368
 
358
369
  safe_join(tokens, " ")
359
370
  end
@@ -5,7 +5,6 @@ module ActionView
5
5
  module Tags # :nodoc:
6
6
  class Base # :nodoc:
7
7
  include Helpers::ActiveModelInstanceTag, Helpers::TagHelper, Helpers::FormTagHelper
8
- include FormOptionsHelper
9
8
 
10
9
  attr_reader :object
11
10
 
@@ -35,22 +34,24 @@ module ActionView
35
34
 
36
35
  private
37
36
  def value
37
+ return unless object
38
+
38
39
  if @allow_method_names_outside_object
39
- object.public_send @method_name if object && object.respond_to?(@method_name)
40
+ object.public_send @method_name if object.respond_to?(@method_name)
40
41
  else
41
- object.public_send @method_name if object
42
+ object.public_send @method_name
42
43
  end
43
44
  end
44
45
 
45
46
  def value_before_type_cast
46
- unless object.nil?
47
- method_before_type_cast = @method_name + "_before_type_cast"
47
+ return unless object
48
48
 
49
- if value_came_from_user? && object.respond_to?(method_before_type_cast)
50
- object.public_send(method_before_type_cast)
51
- else
52
- value
53
- end
49
+ method_before_type_cast = @method_name + "_before_type_cast"
50
+
51
+ if value_came_from_user? && object.respond_to?(method_before_type_cast)
52
+ object.public_send(method_before_type_cast)
53
+ else
54
+ value
54
55
  end
55
56
  end
56
57
 
@@ -120,48 +121,6 @@ module ActionView
120
121
  value.to_s.gsub(/[\s.]/, "_").gsub(/[^-[[:word:]]]/, "").downcase
121
122
  end
122
123
 
123
- def select_content_tag(option_tags, options, html_options)
124
- html_options = html_options.stringify_keys
125
- add_default_name_and_id(html_options)
126
-
127
- if placeholder_required?(html_options)
128
- raise ArgumentError, "include_blank cannot be false for a required field." if options[:include_blank] == false
129
- options[:include_blank] ||= true unless options[:prompt]
130
- end
131
-
132
- value = options.fetch(:selected) { value() }
133
- select = content_tag("select", add_options(option_tags, options, value), html_options)
134
-
135
- if html_options["multiple"] && options.fetch(:include_hidden, true)
136
- tag("input", disabled: html_options["disabled"], name: html_options["name"], type: "hidden", value: "", autocomplete: "off") + select
137
- else
138
- select
139
- end
140
- end
141
-
142
- def placeholder_required?(html_options)
143
- # See https://html.spec.whatwg.org/multipage/forms.html#attr-select-required
144
- html_options["required"] && !html_options["multiple"] && html_options.fetch("size", 1).to_i == 1
145
- end
146
-
147
- def add_options(option_tags, options, value = nil)
148
- if options[:include_blank]
149
- content = (options[:include_blank] if options[:include_blank].is_a?(String))
150
- label = (" " unless content)
151
- option_tags = tag_builder.content_tag_string("option", content, value: "", label: label) + "\n" + option_tags
152
- end
153
-
154
- if value.blank? && options[:prompt]
155
- tag_options = { value: "" }.tap do |prompt_opts|
156
- prompt_opts[:disabled] = true if options[:disabled] == ""
157
- prompt_opts[:selected] = true if options[:selected] == ""
158
- end
159
- option_tags = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), tag_options) + "\n" + option_tags
160
- end
161
-
162
- option_tags
163
- end
164
-
165
124
  def name_and_id_index(options)
166
125
  if options.key?("index")
167
126
  options.delete("index") || ""
@@ -7,6 +7,7 @@ module ActionView
7
7
  module Tags # :nodoc:
8
8
  class CollectionCheckBoxes < Base # :nodoc:
9
9
  include CollectionHelpers
10
+ include FormOptionsHelper
10
11
 
11
12
  class CheckBoxBuilder < Builder # :nodoc:
12
13
  def check_box(extra_html_options = {})
@@ -7,6 +7,7 @@ module ActionView
7
7
  module Tags # :nodoc:
8
8
  class CollectionRadioButtons < Base # :nodoc:
9
9
  include CollectionHelpers
10
+ include FormOptionsHelper
10
11
 
11
12
  class RadioButtonBuilder < Builder # :nodoc:
12
13
  def radio_button(extra_html_options = {})
@@ -4,6 +4,9 @@ module ActionView
4
4
  module Helpers
5
5
  module Tags # :nodoc:
6
6
  class CollectionSelect < Base # :nodoc:
7
+ include SelectRenderer
8
+ include FormOptionsHelper
9
+
7
10
  def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options)
8
11
  @collection = collection
9
12
  @value_method = value_method
@@ -5,7 +5,7 @@ module ActionView
5
5
  module Tags # :nodoc:
6
6
  class DateField < DatetimeField # :nodoc:
7
7
  private
8
- def format_date(value)
8
+ def format_datetime(value)
9
9
  value&.strftime("%Y-%m-%d")
10
10
  end
11
11
  end
@@ -6,6 +6,8 @@ module ActionView
6
6
  module Helpers
7
7
  module Tags # :nodoc:
8
8
  class DateSelect < Base # :nodoc:
9
+ include SelectRenderer
10
+
9
11
  def initialize(object_name, method_name, template_object, options, html_options)
10
12
  @html_options = html_options
11
13
 
@@ -6,20 +6,28 @@ module ActionView
6
6
  class DatetimeField < TextField # :nodoc:
7
7
  def render
8
8
  options = @options.stringify_keys
9
- options["value"] ||= format_date(value)
10
- options["min"] = format_date(datetime_value(options["min"]))
11
- options["max"] = format_date(datetime_value(options["max"]))
9
+ options["value"] = datetime_value(options["value"] || value)
10
+ options["min"] = format_datetime(parse_datetime(options["min"]))
11
+ options["max"] = format_datetime(parse_datetime(options["max"]))
12
12
  @options = options
13
13
  super
14
14
  end
15
15
 
16
16
  private
17
- def format_date(value)
17
+ def datetime_value(value)
18
+ if value.is_a?(String)
19
+ value
20
+ else
21
+ format_datetime(value)
22
+ end
23
+ end
24
+
25
+ def format_datetime(value)
18
26
  raise NotImplementedError
19
27
  end
20
28
 
21
- def datetime_value(value)
22
- if value.is_a? String
29
+ def parse_datetime(value)
30
+ if value.is_a?(String)
23
31
  DateTime.parse(value) rescue nil
24
32
  else
25
33
  value
@@ -4,6 +4,11 @@ module ActionView
4
4
  module Helpers
5
5
  module Tags # :nodoc:
6
6
  class DatetimeLocalField < DatetimeField # :nodoc:
7
+ def initialize(object_name, method_name, template_object, options = {})
8
+ @include_seconds = options.delete(:include_seconds) { true }
9
+ super
10
+ end
11
+
7
12
  class << self
8
13
  def field_type
9
14
  @field_type ||= "datetime-local"
@@ -11,8 +16,12 @@ module ActionView
11
16
  end
12
17
 
13
18
  private
14
- def format_date(value)
15
- value&.strftime("%Y-%m-%dT%T")
19
+ def format_datetime(value)
20
+ if @include_seconds
21
+ value&.strftime("%Y-%m-%dT%T")
22
+ else
23
+ value&.strftime("%Y-%m-%dT%H:%M")
24
+ end
16
25
  end
17
26
  end
18
27
  end
@@ -4,6 +4,9 @@ module ActionView
4
4
  module Helpers
5
5
  module Tags # :nodoc:
6
6
  class GroupedCollectionSelect < Base # :nodoc:
7
+ include SelectRenderer
8
+ include FormOptionsHelper
9
+
7
10
  def initialize(object_name, method_name, template_object, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
8
11
  @collection = collection
9
12
  @group_method = group_method
@@ -5,7 +5,7 @@ module ActionView
5
5
  module Tags # :nodoc:
6
6
  class MonthField < DatetimeField # :nodoc:
7
7
  private
8
- def format_date(value)
8
+ def format_datetime(value)
9
9
  value&.strftime("%Y-%m")
10
10
  end
11
11
  end
@@ -4,6 +4,9 @@ module ActionView
4
4
  module Helpers
5
5
  module Tags # :nodoc:
6
6
  class Select < Base # :nodoc:
7
+ include SelectRenderer
8
+ include FormOptionsHelper
9
+
7
10
  def initialize(object_name, method_name, template_object, choices, options, html_options)
8
11
  @choices = block_given? ? template_object.capture { yield || "" } : choices
9
12
  @choices = @choices.to_a if @choices.is_a?(Range)
@@ -34,7 +37,7 @@ module ActionView
34
37
  # [nil, []]
35
38
  # { nil => [] }
36
39
  def grouped_choices?
37
- !@choices.blank? && @choices.first.respond_to?(:last) && Array === @choices.first.last
40
+ !@choices.blank? && @choices.first.respond_to?(:second) && Array === @choices.first.second
38
41
  end
39
42
  end
40
43
  end