actionview 7.0.8.3 → 7.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +235 -372
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/app/assets/javascripts/rails-ujs.esm.js +668 -0
  6. data/app/assets/javascripts/rails-ujs.js +606 -0
  7. data/lib/action_view/base.rb +28 -7
  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 +4 -4
  14. data/lib/action_view/helpers/active_model_helper.rb +1 -1
  15. data/lib/action_view/helpers/asset_tag_helper.rb +130 -46
  16. data/lib/action_view/helpers/asset_url_helper.rb +6 -5
  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 +24 -10
  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 +2 -2
  24. data/lib/action_view/helpers/date_helper.rb +17 -19
  25. data/lib/action_view/helpers/debug_helper.rb +3 -3
  26. data/lib/action_view/helpers/form_helper.rb +43 -18
  27. data/lib/action_view/helpers/form_options_helper.rb +2 -1
  28. data/lib/action_view/helpers/form_tag_helper.rb +43 -9
  29. data/lib/action_view/helpers/javascript_helper.rb +1 -0
  30. data/lib/action_view/helpers/number_helper.rb +2 -1
  31. data/lib/action_view/helpers/output_safety_helper.rb +2 -2
  32. data/lib/action_view/helpers/rendering_helper.rb +1 -1
  33. data/lib/action_view/helpers/sanitize_helper.rb +33 -14
  34. data/lib/action_view/helpers/tag_helper.rb +5 -27
  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 +3 -0
  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 +32 -16
  53. data/lib/action_view/helpers/translation_helper.rb +3 -3
  54. data/lib/action_view/helpers/url_helper.rb +41 -14
  55. data/lib/action_view/helpers.rb +2 -0
  56. data/lib/action_view/layouts.rb +4 -2
  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 +15 -8
  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 +2 -1
  66. data/lib/action_view/renderer/partial_renderer.rb +2 -1
  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 +22 -4
  71. data/lib/action_view/ripper_ast_parser.rb +5 -5
  72. data/lib/action_view/template/error.rb +14 -1
  73. data/lib/action_view/template/handlers/builder.rb +4 -4
  74. data/lib/action_view/template/handlers/erb/erubi.rb +23 -27
  75. data/lib/action_view/template/handlers/erb.rb +73 -1
  76. data/lib/action_view/template/handlers.rb +1 -1
  77. data/lib/action_view/template/html.rb +1 -1
  78. data/lib/action_view/template/raw_file.rb +1 -1
  79. data/lib/action_view/template/renderable.rb +1 -1
  80. data/lib/action_view/template/resolver.rb +10 -2
  81. data/lib/action_view/template/text.rb +1 -1
  82. data/lib/action_view/template/types.rb +25 -34
  83. data/lib/action_view/template.rb +179 -52
  84. data/lib/action_view/template_path.rb +2 -0
  85. data/lib/action_view/test_case.rb +8 -5
  86. data/lib/action_view/unbound_template.rb +15 -5
  87. data/lib/action_view/version.rb +1 -1
  88. data/lib/action_view/view_paths.rb +15 -24
  89. data/lib/action_view.rb +4 -1
  90. metadata +26 -26
@@ -1,14 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "cgi"
4
+ require "action_view/helpers/content_exfiltration_prevention_helper"
4
5
  require "action_view/helpers/url_helper"
5
6
  require "action_view/helpers/text_helper"
6
7
  require "active_support/core_ext/string/output_safety"
7
8
  require "active_support/core_ext/module/attribute_accessors"
8
9
 
9
10
  module ActionView
10
- # = Action View Form Tag Helpers
11
11
  module Helpers # :nodoc:
12
+ # = Action View Form Tag \Helpers
13
+ #
12
14
  # Provides a number of methods for creating form tags that don't rely on an Active Record object assigned to the template like
13
15
  # FormHelper does. Instead, you provide the names and values manually.
14
16
  #
@@ -19,6 +21,7 @@ module ActionView
19
21
 
20
22
  include UrlHelper
21
23
  include TextHelper
24
+ include ContentExfiltrationPreventionHelper
22
25
 
23
26
  mattr_accessor :embed_authenticity_token_in_remote_forms
24
27
  self.embed_authenticity_token_in_remote_forms = nil
@@ -420,9 +423,17 @@ module ActionView
420
423
  content_tag :textarea, content.to_s.html_safe, { "name" => name, "id" => sanitize_to_id(name) }.update(options)
421
424
  end
422
425
 
426
+ ##
427
+ # :call-seq:
428
+ # check_box_tag(name, options = {})
429
+ # check_box_tag(name, value, options = {})
430
+ # check_box_tag(name, value, checked, options = {})
431
+ #
423
432
  # Creates a check box form input tag.
424
433
  #
425
434
  # ==== Options
435
+ # * <tt>:value</tt> - The value of the input. Defaults to <tt>"1"</tt>.
436
+ # * <tt>:checked</tt> - If set to true, the checkbox will be checked by default.
426
437
  # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
427
438
  # * Any other key creates standard HTML options for the tag.
428
439
  #
@@ -441,16 +452,27 @@ module ActionView
441
452
  #
442
453
  # check_box_tag 'eula', 'accepted', false, disabled: true
443
454
  # # => <input disabled="disabled" id="eula" name="eula" type="checkbox" value="accepted" />
444
- def check_box_tag(name, value = "1", checked = false, options = {})
455
+ def check_box_tag(name, *args)
456
+ if args.length >= 4
457
+ raise ArgumentError, "wrong number of arguments (given #{args.length + 1}, expected 1..4)"
458
+ end
459
+ options = args.extract_options!
460
+ value, checked = args.empty? ? ["1", false] : [*args, false]
445
461
  html_options = { "type" => "checkbox", "name" => name, "id" => sanitize_to_id(name), "value" => value }.update(options.stringify_keys)
446
462
  html_options["checked"] = "checked" if checked
447
463
  tag :input, html_options
448
464
  end
449
465
 
466
+ ##
467
+ # :call-seq:
468
+ # radio_button_tag(name, value, options = {})
469
+ # radio_button_tag(name, value, checked, options = {})
470
+ #
450
471
  # Creates a radio button; use groups of radio buttons named the same to allow users to
451
472
  # select from a group of options.
452
473
  #
453
474
  # ==== Options
475
+ # * <tt>:checked</tt> - If set to true, the radio button will be selected by default.
454
476
  # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
455
477
  # * Any other key creates standard HTML options for the tag.
456
478
  #
@@ -466,7 +488,12 @@ module ActionView
466
488
  #
467
489
  # radio_button_tag 'color', "green", true, class: "color_input"
468
490
  # # => <input checked="checked" class="color_input" id="color_green" name="color" type="radio" value="green" />
469
- def radio_button_tag(name, value, checked = false, options = {})
491
+ def radio_button_tag(name, value, *args)
492
+ if args.length >= 3
493
+ raise ArgumentError, "wrong number of arguments (given #{args.length + 2}, expected 2..4)"
494
+ end
495
+ options = args.extract_options!
496
+ checked = args.empty? ? false : args.first
470
497
  html_options = { "type" => "radio", "name" => name, "id" => "#{sanitize_to_id(name)}_#{sanitize_to_id(value)}", "value" => value }.update(options.stringify_keys)
471
498
  html_options["checked"] = "checked" if checked
472
499
  tag :input, html_options
@@ -495,9 +522,9 @@ module ActionView
495
522
  # submit_tag "Edit", class: "edit_button"
496
523
  # # => <input class="edit_button" data-disable-with="Edit" name="commit" type="submit" value="Edit" />
497
524
  #
498
- # ==== Deprecated: Rails UJS attributes
525
+ # ==== Deprecated: \Rails UJS attributes
499
526
  #
500
- # Prior to Rails 7, Rails shipped with the JavaScript library called @rails/ujs on by default. Following Rails 7,
527
+ # Prior to \Rails 7, \Rails shipped with the JavaScript library called @rails/ujs on by default. Following \Rails 7,
501
528
  # this library is no longer on by default. This library integrated with the following options:
502
529
  #
503
530
  # * <tt>confirm: 'question?'</tt> - If present the unobtrusive JavaScript
@@ -555,9 +582,9 @@ module ActionView
555
582
  # # <strong>Ask me!</strong>
556
583
  # # </button>
557
584
  #
558
- # ==== Deprecated: Rails UJS attributes
585
+ # ==== Deprecated: \Rails UJS attributes
559
586
  #
560
- # Prior to Rails 7, Rails shipped with a JavaScript library called @rails/ujs on by default. Following Rails 7,
587
+ # Prior to \Rails 7, \Rails shipped with a JavaScript library called @rails/ujs on by default. Following \Rails 7,
561
588
  # this library is no longer on by default. This library integrated with the following options:
562
589
  #
563
590
  # * <tt>confirm: 'question?'</tt> - If present, the
@@ -770,6 +797,7 @@ module ActionView
770
797
  # * <tt>:min</tt> - The minimum acceptable value.
771
798
  # * <tt>:max</tt> - The maximum acceptable value.
772
799
  # * <tt>:step</tt> - The acceptable value granularity.
800
+ # * <tt>:include_seconds</tt> - Include seconds in the output timestamp format (true by default).
773
801
  def datetime_field_tag(name, value = nil, options = {})
774
802
  text_field_tag(name, value, options.merge(type: "datetime-local"))
775
803
  end
@@ -979,7 +1007,8 @@ module ActionView
979
1007
 
980
1008
  def form_tag_html(html_options)
981
1009
  extra_tags = extra_tags_for_form(html_options)
982
- tag(:form, html_options, true) + extra_tags
1010
+ html = tag(:form, html_options, true) + extra_tags
1011
+ prevent_content_exfiltration(html)
983
1012
  end
984
1013
 
985
1014
  def form_tag_with_body(html_options, content)
@@ -1009,9 +1038,14 @@ module ActionView
1009
1038
  end
1010
1039
 
1011
1040
  def convert_direct_upload_option_to_url(options)
1012
- if options.delete(:direct_upload) && respond_to?(:rails_direct_uploads_url)
1041
+ return options unless options.delete(:direct_upload)
1042
+
1043
+ if respond_to?(:rails_direct_uploads_url)
1013
1044
  options["data-direct-upload-url"] = rails_direct_uploads_url
1045
+ elsif respond_to?(:main_app) && main_app.respond_to?(:rails_direct_uploads_url)
1046
+ options["data-direct-upload-url"] = main_app.rails_direct_uploads_url
1014
1047
  end
1048
+
1015
1049
  options
1016
1050
  end
1017
1051
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module ActionView
4
4
  module Helpers # :nodoc:
5
+ # = Action View JavaScript \Helpers
5
6
  module JavaScriptHelper
6
7
  JS_ESCAPE_MAP = {
7
8
  "\\" => "\\\\",
@@ -5,8 +5,9 @@ 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
13
  # precision, positional notation, file size, and pretty printing.
@@ -3,11 +3,11 @@
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
  #
@@ -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
@@ -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
@@ -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
@@ -65,9 +66,7 @@ module ActionView
65
66
  tag_string(:p, *arguments, **options, &block)
66
67
  end
67
68
 
68
- def tag_string(name, content = nil, **options, &block)
69
- escape = handle_deprecated_escape_options(options)
70
-
69
+ def tag_string(name, content = nil, escape: true, **options, &block)
71
70
  content = @view_context.capture(self, &block) if block_given?
72
71
  self_closing = SVG_SELF_CLOSING_ELEMENTS.include?(name)
73
72
  if (HTML_VOID_ELEMENTS.include?(name) || self_closing) && content.nil?
@@ -164,27 +163,6 @@ module ActionView
164
163
  true
165
164
  end
166
165
 
167
- def handle_deprecated_escape_options(options)
168
- # The option :escape_attributes has been merged into the options hash to be
169
- # able to warn when it is used, so we need to handle default values here.
170
- escape_option_provided = options.has_key?(:escape)
171
- escape_attributes_option_provided = options.has_key?(:escape_attributes)
172
-
173
- if escape_attributes_option_provided
174
- ActiveSupport::Deprecation.warn(<<~MSG)
175
- Use of the option :escape_attributes is deprecated. It currently \
176
- escapes both names and values of tags and attributes and it is \
177
- equivalent to :escape. If any of them are enabled, the escaping \
178
- is fully enabled.
179
- MSG
180
- end
181
-
182
- return true unless escape_option_provided || escape_attributes_option_provided
183
- escape_option = options.delete(:escape)
184
- escape_attributes_option = options.delete(:escape_attributes)
185
- escape_option || escape_attributes_option
186
- end
187
-
188
166
  def method_missing(called, *args, **options, &block)
189
167
  tag_string(called, *args, **options, &block)
190
168
  end
@@ -283,7 +261,7 @@ module ActionView
283
261
  #
284
262
  # === Legacy syntax
285
263
  #
286
- # 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.
287
265
  #
288
266
  # tag(name, options = nil, open = false, escape = true)
289
267
  #
@@ -386,7 +364,7 @@ module ActionView
386
364
  # token_list(nil, false, 123, "", "foo", { bar: true })
387
365
  # # => "123 foo bar"
388
366
  def token_list(*args)
389
- 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
390
368
 
391
369
  safe_join(tokens, " ")
392
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)
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module Helpers
5
+ module Tags # :nodoc:
6
+ module SelectRenderer # :nodoc:
7
+ private
8
+ def select_content_tag(option_tags, options, html_options)
9
+ html_options = html_options.stringify_keys
10
+ [:required, :multiple, :size].each do |prop|
11
+ html_options[prop.to_s] = options.delete(prop) if options.key?(prop) && !html_options.key?(prop.to_s)
12
+ end
13
+
14
+ add_default_name_and_id(html_options)
15
+
16
+ if placeholder_required?(html_options)
17
+ raise ArgumentError, "include_blank cannot be false for a required field." if options[:include_blank] == false
18
+ options[:include_blank] ||= true unless options[:prompt]
19
+ end
20
+
21
+ value = options.fetch(:selected) { value() }
22
+ select = content_tag("select", add_options(option_tags, options, value), html_options)
23
+
24
+ if html_options["multiple"] && options.fetch(:include_hidden, true)
25
+ tag("input", disabled: html_options["disabled"], name: html_options["name"], type: "hidden", value: "", autocomplete: "off") + select
26
+ else
27
+ select
28
+ end
29
+ end
30
+
31
+ def placeholder_required?(html_options)
32
+ # See https://html.spec.whatwg.org/multipage/forms.html#attr-select-required
33
+ html_options["required"] && !html_options["multiple"] && html_options.fetch("size", 1).to_i == 1
34
+ end
35
+
36
+ def add_options(option_tags, options, value = nil)
37
+ if options[:include_blank]
38
+ content = (options[:include_blank] if options[:include_blank].is_a?(String))
39
+ label = (" " unless content)
40
+ option_tags = tag_builder.content_tag_string("option", content, value: "", label: label) + "\n" + option_tags
41
+ end
42
+
43
+ if value.blank? && options[:prompt]
44
+ tag_options = { value: "" }.tap do |prompt_opts|
45
+ prompt_opts[:disabled] = true if options[:disabled] == ""
46
+ prompt_opts[:selected] = true if options[:selected] == ""
47
+ end
48
+ option_tags = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), tag_options) + "\n" + option_tags
49
+ end
50
+
51
+ option_tags
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end