actionview 7.1.5.2 → 8.1.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +96 -436
- data/README.rdoc +1 -1
- data/lib/action_view/base.rb +29 -11
- data/lib/action_view/buffers.rb +1 -1
- data/lib/action_view/cache_expiry.rb +9 -3
- data/lib/action_view/dependency_tracker/erb_tracker.rb +37 -28
- data/lib/action_view/dependency_tracker/ruby_tracker.rb +43 -0
- data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
- data/lib/action_view/dependency_tracker.rb +8 -2
- data/lib/action_view/digestor.rb +6 -2
- data/lib/action_view/gem_version.rb +3 -3
- data/lib/action_view/helpers/asset_tag_helper.rb +39 -8
- data/lib/action_view/helpers/atom_feed_helper.rb +1 -3
- data/lib/action_view/helpers/cache_helper.rb +10 -2
- data/lib/action_view/helpers/capture_helper.rb +2 -2
- data/lib/action_view/helpers/controller_helper.rb +6 -2
- data/lib/action_view/helpers/csrf_helper.rb +1 -1
- data/lib/action_view/helpers/date_helper.rb +28 -4
- data/lib/action_view/helpers/form_helper.rb +277 -272
- data/lib/action_view/helpers/form_options_helper.rb +39 -35
- data/lib/action_view/helpers/form_tag_helper.rb +115 -72
- data/lib/action_view/helpers/javascript_helper.rb +5 -1
- data/lib/action_view/helpers/number_helper.rb +14 -0
- data/lib/action_view/helpers/output_safety_helper.rb +5 -6
- data/lib/action_view/helpers/rendering_helper.rb +160 -50
- data/lib/action_view/helpers/sanitize_helper.rb +6 -0
- data/lib/action_view/helpers/tag_helper.rb +225 -48
- data/lib/action_view/helpers/tags/base.rb +11 -9
- data/lib/action_view/helpers/tags/check_box.rb +9 -3
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +4 -3
- data/lib/action_view/helpers/tags/collection_helpers.rb +2 -1
- data/lib/action_view/helpers/tags/datetime_field.rb +1 -1
- data/lib/action_view/helpers/tags/file_field.rb +7 -2
- data/lib/action_view/helpers/tags/hidden_field.rb +1 -1
- data/lib/action_view/helpers/tags/label.rb +3 -10
- data/lib/action_view/helpers/tags/radio_button.rb +1 -1
- data/lib/action_view/helpers/tags/select.rb +6 -1
- data/lib/action_view/helpers/tags/select_renderer.rb +6 -4
- data/lib/action_view/helpers/tags/text_area.rb +1 -1
- data/lib/action_view/helpers/tags/text_field.rb +1 -1
- data/lib/action_view/helpers/text_helper.rb +11 -4
- data/lib/action_view/helpers/translation_helper.rb +6 -1
- data/lib/action_view/helpers/url_helper.rb +42 -90
- data/lib/action_view/layouts.rb +9 -11
- data/lib/action_view/locale/en.yml +3 -0
- data/lib/action_view/log_subscriber.rb +9 -8
- data/lib/action_view/railtie.rb +12 -2
- data/lib/action_view/record_identifier.rb +22 -1
- data/lib/action_view/render_parser/prism_render_parser.rb +139 -0
- data/lib/action_view/{ripper_ast_parser.rb → render_parser/ripper_render_parser.rb} +162 -10
- data/lib/action_view/render_parser.rb +21 -169
- data/lib/action_view/renderer/abstract_renderer.rb +1 -1
- data/lib/action_view/renderer/partial_renderer.rb +18 -2
- data/lib/action_view/renderer/renderer.rb +32 -38
- data/lib/action_view/renderer/streaming_template_renderer.rb +8 -2
- data/lib/action_view/renderer/template_renderer.rb +3 -3
- data/lib/action_view/rendering.rb +6 -7
- data/lib/action_view/structured_event_subscriber.rb +97 -0
- data/lib/action_view/template/error.rb +18 -3
- data/lib/action_view/template/handlers/erb/erubi.rb +1 -1
- data/lib/action_view/template/handlers/erb.rb +77 -44
- data/lib/action_view/template/raw_file.rb +4 -0
- data/lib/action_view/template/renderable.rb +7 -1
- data/lib/action_view/template/resolver.rb +0 -3
- data/lib/action_view/template.rb +39 -12
- data/lib/action_view/test_case.rb +57 -62
- data/lib/action_view.rb +4 -0
- metadata +17 -14
- data/lib/action_view/dependency_tracker/ripper_tracker.rb +0 -59
data/lib/action_view/base.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "active_support/core_ext/module/attr_internal"
|
|
|
4
4
|
require "active_support/core_ext/module/attribute_accessors"
|
|
5
5
|
require "active_support/ordered_options"
|
|
6
6
|
require "action_view/log_subscriber"
|
|
7
|
+
require "action_view/structured_event_subscriber"
|
|
7
8
|
require "action_view/helpers"
|
|
8
9
|
require "action_view/context"
|
|
9
10
|
require "action_view/template"
|
|
@@ -80,6 +81,23 @@ module ActionView # :nodoc:
|
|
|
80
81
|
# This is useful in cases where you aren't sure if the local variable has been assigned. Alternatively, you could also use
|
|
81
82
|
# <tt>defined? headline</tt> to first check if the variable has been assigned before using it.
|
|
82
83
|
#
|
|
84
|
+
# By default, templates will accept any <tt>locals</tt> as keyword arguments. To restrict what <tt>locals</tt> a template accepts, add a <tt>locals:</tt> magic comment:
|
|
85
|
+
#
|
|
86
|
+
# <%# locals: (headline:) %>
|
|
87
|
+
#
|
|
88
|
+
# Headline: <%= headline %>
|
|
89
|
+
#
|
|
90
|
+
# In cases where the local variables are optional, declare the keyword argument with a default value:
|
|
91
|
+
#
|
|
92
|
+
# <%# locals: (headline: nil) %>
|
|
93
|
+
#
|
|
94
|
+
# <% unless headline.nil? %>
|
|
95
|
+
# Headline: <%= headline %>
|
|
96
|
+
# <% end %>
|
|
97
|
+
#
|
|
98
|
+
# Read more about strict locals in {Action View Overview}[https://guides.rubyonrails.org/action_view_overview.html#strict-locals]
|
|
99
|
+
# in the guides.
|
|
100
|
+
#
|
|
83
101
|
# === Template caching
|
|
84
102
|
#
|
|
85
103
|
# By default, \Rails will compile each template to a method in order to render it. When you alter a template,
|
|
@@ -164,6 +182,10 @@ module ActionView # :nodoc:
|
|
|
164
182
|
class_attribute :_routes
|
|
165
183
|
class_attribute :logger
|
|
166
184
|
|
|
185
|
+
# Specify whether to omit autocomplete="off" on hidden inputs generated by helpers.
|
|
186
|
+
# Configured via `config.action_view.remove_hidden_field_autocomplete`
|
|
187
|
+
cattr_accessor :remove_hidden_field_autocomplete, default: false
|
|
188
|
+
|
|
167
189
|
class << self
|
|
168
190
|
delegate :erb_trim_mode=, to: "ActionView::Template::Handlers::ERB"
|
|
169
191
|
|
|
@@ -225,8 +247,6 @@ module ActionView # :nodoc:
|
|
|
225
247
|
# :startdoc:
|
|
226
248
|
|
|
227
249
|
def initialize(lookup_context, assigns, controller) # :nodoc:
|
|
228
|
-
@_config = ActiveSupport::InheritableOptions.new
|
|
229
|
-
|
|
230
250
|
@lookup_context = lookup_context
|
|
231
251
|
|
|
232
252
|
@view_renderer = ActionView::Renderer.new @lookup_context
|
|
@@ -248,16 +268,14 @@ module ActionView # :nodoc:
|
|
|
248
268
|
|
|
249
269
|
if has_strict_locals
|
|
250
270
|
begin
|
|
251
|
-
public_send(method, buffer, **locals, &block)
|
|
271
|
+
public_send(method, locals, buffer, **locals, &block)
|
|
252
272
|
rescue ArgumentError => argument_error
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
gsub("no keywords accepted", "no locals accepted")
|
|
260
|
-
)
|
|
273
|
+
public_send_line = __LINE__ - 2
|
|
274
|
+
frame = argument_error.backtrace_locations[1]
|
|
275
|
+
if frame.path == __FILE__ && frame.lineno == public_send_line
|
|
276
|
+
raise StrictLocalsError.new(argument_error, @current_template)
|
|
277
|
+
end
|
|
278
|
+
raise
|
|
261
279
|
end
|
|
262
280
|
else
|
|
263
281
|
public_send(method, locals, buffer, &block)
|
data/lib/action_view/buffers.rb
CHANGED
|
@@ -10,16 +10,17 @@ module ActionView
|
|
|
10
10
|
@watcher = nil
|
|
11
11
|
@previous_change = false
|
|
12
12
|
|
|
13
|
-
rebuild_watcher
|
|
14
|
-
|
|
15
13
|
ActionView::PathRegistry.file_system_resolver_hooks << method(:rebuild_watcher)
|
|
16
14
|
end
|
|
17
15
|
|
|
18
16
|
def updated?
|
|
17
|
+
build_watcher unless @watcher
|
|
19
18
|
@previous_change || @watcher.updated?
|
|
20
19
|
end
|
|
21
20
|
|
|
22
21
|
def execute
|
|
22
|
+
return unless @watcher
|
|
23
|
+
|
|
23
24
|
watcher = nil
|
|
24
25
|
@mutex.synchronize do
|
|
25
26
|
@previous_change = false
|
|
@@ -33,7 +34,7 @@ module ActionView
|
|
|
33
34
|
ActionView::LookupContext::DetailsKey.clear
|
|
34
35
|
end
|
|
35
36
|
|
|
36
|
-
def
|
|
37
|
+
def build_watcher
|
|
37
38
|
@mutex.synchronize do
|
|
38
39
|
old_watcher = @watcher
|
|
39
40
|
|
|
@@ -51,6 +52,11 @@ module ActionView
|
|
|
51
52
|
end
|
|
52
53
|
end
|
|
53
54
|
|
|
55
|
+
def rebuild_watcher
|
|
56
|
+
return unless @watcher
|
|
57
|
+
build_watcher
|
|
58
|
+
end
|
|
59
|
+
|
|
54
60
|
def dirs_to_watch
|
|
55
61
|
all_view_paths.uniq.sort
|
|
56
62
|
end
|
|
@@ -74,7 +74,7 @@ module ActionView
|
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
def dependencies
|
|
77
|
-
render_dependencies + explicit_dependencies
|
|
77
|
+
WildcardResolver.new(@view_paths, render_dependencies + explicit_dependencies).resolve
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
attr_reader :name, :template
|
|
@@ -90,15 +90,15 @@ module ActionView
|
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
def render_dependencies
|
|
93
|
-
|
|
94
|
-
render_calls = source.
|
|
93
|
+
dependencies = []
|
|
94
|
+
render_calls = source.scan(/<%(?:(?:(?!<%).)*?\brender\b((?:(?!%>).)*?))%>/m).flatten
|
|
95
95
|
|
|
96
96
|
render_calls.each do |arguments|
|
|
97
|
-
add_dependencies(
|
|
98
|
-
add_dependencies(
|
|
97
|
+
add_dependencies(dependencies, arguments, LAYOUT_DEPENDENCY)
|
|
98
|
+
add_dependencies(dependencies, arguments, RENDER_ARGUMENTS)
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
dependencies
|
|
102
102
|
end
|
|
103
103
|
|
|
104
104
|
def add_dependencies(render_dependencies, arguments, pattern)
|
|
@@ -116,12 +116,37 @@ module ActionView
|
|
|
116
116
|
end
|
|
117
117
|
|
|
118
118
|
def add_static_dependency(dependencies, dependency, quote_type)
|
|
119
|
-
if quote_type == '"'
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
119
|
+
if quote_type == '"' && dependency.include?('#{')
|
|
120
|
+
scanner = StringScanner.new(dependency)
|
|
121
|
+
|
|
122
|
+
wildcard_dependency = +""
|
|
123
|
+
|
|
124
|
+
while !scanner.eos?
|
|
125
|
+
if scanner.scan_until(/\#{/)
|
|
126
|
+
unmatched_brackets = 1
|
|
127
|
+
wildcard_dependency << scanner.pre_match
|
|
128
|
+
|
|
129
|
+
while unmatched_brackets > 0 && !scanner.eos?
|
|
130
|
+
found = scanner.scan_until(/[{}]/)
|
|
131
|
+
return unless found
|
|
132
|
+
|
|
133
|
+
case scanner.matched
|
|
134
|
+
when "{"
|
|
135
|
+
unmatched_brackets += 1
|
|
136
|
+
when "}"
|
|
137
|
+
unmatched_brackets -= 1
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
wildcard_dependency << "*"
|
|
142
|
+
else
|
|
143
|
+
wildcard_dependency << scanner.rest
|
|
144
|
+
scanner.terminate
|
|
145
|
+
end
|
|
146
|
+
end
|
|
123
147
|
|
|
124
|
-
|
|
148
|
+
dependencies << wildcard_dependency
|
|
149
|
+
elsif dependency
|
|
125
150
|
if dependency.include?("/")
|
|
126
151
|
dependencies << dependency
|
|
127
152
|
else
|
|
@@ -130,24 +155,8 @@ module ActionView
|
|
|
130
155
|
end
|
|
131
156
|
end
|
|
132
157
|
|
|
133
|
-
def resolve_directories(wildcard_dependencies)
|
|
134
|
-
return [] unless @view_paths
|
|
135
|
-
return [] if wildcard_dependencies.empty?
|
|
136
|
-
|
|
137
|
-
# Remove trailing "/*"
|
|
138
|
-
prefixes = wildcard_dependencies.map { |query| query[0..-3] }
|
|
139
|
-
|
|
140
|
-
@view_paths.flat_map(&:all_template_paths).uniq.filter_map { |path|
|
|
141
|
-
path.to_s if prefixes.include?(path.prefix)
|
|
142
|
-
}.sort
|
|
143
|
-
end
|
|
144
|
-
|
|
145
158
|
def explicit_dependencies
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
wildcards, explicits = dependencies.partition { |dependency| dependency.end_with?("/*") }
|
|
149
|
-
|
|
150
|
-
(explicits + resolve_directories(wildcards)).uniq
|
|
159
|
+
source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
|
|
151
160
|
end
|
|
152
161
|
end
|
|
153
162
|
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActionView
|
|
4
|
+
class DependencyTracker # :nodoc:
|
|
5
|
+
class RubyTracker # :nodoc:
|
|
6
|
+
EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/
|
|
7
|
+
|
|
8
|
+
def self.call(name, template, view_paths = nil)
|
|
9
|
+
new(name, template, view_paths).dependencies
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def dependencies
|
|
13
|
+
WildcardResolver.new(view_paths, render_dependencies + explicit_dependencies).resolve
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.supports_view_paths? # :nodoc:
|
|
17
|
+
true
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def initialize(name, template, view_paths = nil, parser_class: RenderParser::Default)
|
|
21
|
+
@name, @template, @view_paths = name, template, view_paths
|
|
22
|
+
@parser_class = parser_class
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
attr_reader :template, :name, :view_paths
|
|
27
|
+
|
|
28
|
+
def render_dependencies
|
|
29
|
+
return [] unless template.source.include?("render")
|
|
30
|
+
|
|
31
|
+
compiled_source = template.handler.call(template, template.source)
|
|
32
|
+
|
|
33
|
+
@parser_class.new(@name, compiled_source).render_calls.filter_map do |render_call|
|
|
34
|
+
render_call.gsub(%r|/_|, "/")
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def explicit_dependencies
|
|
39
|
+
template.source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActionView
|
|
4
|
+
class DependencyTracker # :nodoc:
|
|
5
|
+
class WildcardResolver # :nodoc:
|
|
6
|
+
def initialize(view_paths, dependencies)
|
|
7
|
+
@view_paths = view_paths
|
|
8
|
+
|
|
9
|
+
@wildcard_dependencies, @explicit_dependencies =
|
|
10
|
+
dependencies.partition { |dependency| dependency.end_with?("/*") }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def resolve
|
|
14
|
+
return explicit_dependencies.uniq if !view_paths || wildcard_dependencies.empty?
|
|
15
|
+
|
|
16
|
+
(explicit_dependencies + resolved_wildcard_dependencies).uniq
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
attr_reader :explicit_dependencies, :wildcard_dependencies, :view_paths
|
|
21
|
+
|
|
22
|
+
def resolved_wildcard_dependencies
|
|
23
|
+
# Remove trailing "/*"
|
|
24
|
+
prefixes = wildcard_dependencies.map { |query| query[0..-3] }
|
|
25
|
+
|
|
26
|
+
view_paths.flat_map(&:all_template_paths).uniq.filter_map { |path|
|
|
27
|
+
path.to_s if prefixes.include?(path.prefix)
|
|
28
|
+
}.sort
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -9,7 +9,8 @@ module ActionView
|
|
|
9
9
|
extend ActiveSupport::Autoload
|
|
10
10
|
|
|
11
11
|
autoload :ERBTracker
|
|
12
|
-
autoload :
|
|
12
|
+
autoload :RubyTracker
|
|
13
|
+
autoload :WildcardResolver
|
|
13
14
|
|
|
14
15
|
@trackers = Concurrent::Map.new
|
|
15
16
|
|
|
@@ -35,6 +36,11 @@ module ActionView
|
|
|
35
36
|
@trackers.delete(handler)
|
|
36
37
|
end
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
case ActionView.render_tracker
|
|
40
|
+
when :ruby
|
|
41
|
+
register_tracker :erb, RubyTracker
|
|
42
|
+
else
|
|
43
|
+
register_tracker :erb, ERBTracker
|
|
44
|
+
end
|
|
39
45
|
end
|
|
40
46
|
end
|
data/lib/action_view/digestor.rb
CHANGED
|
@@ -107,8 +107,12 @@ module ActionView
|
|
|
107
107
|
end.join("-")
|
|
108
108
|
end
|
|
109
109
|
|
|
110
|
-
def to_dep_map
|
|
111
|
-
|
|
110
|
+
def to_dep_map(seen = Set.new.compare_by_identity)
|
|
111
|
+
if seen.add?(self)
|
|
112
|
+
children.any? ? { name => children.map { |c| c.to_dep_map(seen) } } : name
|
|
113
|
+
else # the tree has a cycle
|
|
114
|
+
name
|
|
115
|
+
end
|
|
112
116
|
end
|
|
113
117
|
end
|
|
114
118
|
|
|
@@ -26,6 +26,8 @@ module ActionView
|
|
|
26
26
|
mattr_accessor :image_decoding
|
|
27
27
|
mattr_accessor :preload_links_header
|
|
28
28
|
mattr_accessor :apply_stylesheet_media_default
|
|
29
|
+
mattr_accessor :auto_include_nonce_for_scripts
|
|
30
|
+
mattr_accessor :auto_include_nonce_for_styles
|
|
29
31
|
|
|
30
32
|
# Returns an HTML script tag for each of the +sources+ provided.
|
|
31
33
|
#
|
|
@@ -68,6 +70,8 @@ module ActionView
|
|
|
68
70
|
# attribute, which indicates to the browser that the script is meant to
|
|
69
71
|
# be executed after the document has been parsed. Additionally, prevents
|
|
70
72
|
# sending the Preload Links header.
|
|
73
|
+
# * <tt>:nopush</tt> - Specify if the use of server push is not desired
|
|
74
|
+
# for the script. Defaults to +true+.
|
|
71
75
|
#
|
|
72
76
|
# Any other specified options will be treated as HTML attributes for the
|
|
73
77
|
# +script+ tag.
|
|
@@ -113,11 +117,11 @@ module ActionView
|
|
|
113
117
|
path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
|
|
114
118
|
preload_links = []
|
|
115
119
|
use_preload_links_header = options["preload_links_header"].nil? ? preload_links_header : options.delete("preload_links_header")
|
|
116
|
-
nopush = options["nopush"].nil?
|
|
120
|
+
nopush = options["nopush"].nil? || options.delete("nopush")
|
|
117
121
|
crossorigin = options.delete("crossorigin")
|
|
118
122
|
crossorigin = "anonymous" if crossorigin == true
|
|
119
123
|
integrity = options["integrity"]
|
|
120
|
-
rel = options["type"] == "module" ? "modulepreload" : "preload"
|
|
124
|
+
rel = options["type"] == "module" || options["type"] == :module ? "modulepreload" : "preload"
|
|
121
125
|
|
|
122
126
|
sources_tags = sources.uniq.map { |source|
|
|
123
127
|
href = path_to_javascript(source, path_options)
|
|
@@ -125,6 +129,7 @@ module ActionView
|
|
|
125
129
|
preload_link = "<#{href}>; rel=#{rel}; as=script"
|
|
126
130
|
preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil?
|
|
127
131
|
preload_link += "; integrity=#{integrity}" unless integrity.nil?
|
|
132
|
+
preload_link += "; nonce=#{content_security_policy_nonce}" if options["nonce"] == true
|
|
128
133
|
preload_link += "; nopush" if nopush
|
|
129
134
|
preload_links << preload_link
|
|
130
135
|
end
|
|
@@ -132,8 +137,10 @@ module ActionView
|
|
|
132
137
|
"src" => href,
|
|
133
138
|
"crossorigin" => crossorigin
|
|
134
139
|
}.merge!(options)
|
|
135
|
-
if tag_options["nonce"] == true
|
|
140
|
+
if tag_options["nonce"] == true || (!tag_options.key?("nonce") && auto_include_nonce_for_scripts)
|
|
136
141
|
tag_options["nonce"] = content_security_policy_nonce
|
|
142
|
+
elsif tag_options["nonce"] == false
|
|
143
|
+
tag_options.delete("nonce")
|
|
137
144
|
end
|
|
138
145
|
content_tag("script", "", tag_options)
|
|
139
146
|
}.join("\n").html_safe
|
|
@@ -166,6 +173,10 @@ module ActionView
|
|
|
166
173
|
# that path.
|
|
167
174
|
# * <tt>:skip_pipeline</tt> - This option is used to bypass the asset pipeline
|
|
168
175
|
# when it is set to true.
|
|
176
|
+
# * <tt>:nonce</tt> - When set to true, adds an automatic nonce value if
|
|
177
|
+
# you have Content Security Policy enabled.
|
|
178
|
+
# * <tt>:nopush</tt> - Specify if the use of server push is not desired
|
|
179
|
+
# for the stylesheet. Defaults to +true+.
|
|
169
180
|
#
|
|
170
181
|
# ==== Examples
|
|
171
182
|
#
|
|
@@ -190,6 +201,9 @@ module ActionView
|
|
|
190
201
|
# stylesheet_link_tag "random.styles", "/css/stylish"
|
|
191
202
|
# # => <link href="/assets/random.styles" rel="stylesheet" />
|
|
192
203
|
# # <link href="/css/stylish.css" rel="stylesheet" />
|
|
204
|
+
#
|
|
205
|
+
# stylesheet_link_tag "style", nonce: true
|
|
206
|
+
# # => <link href="/assets/style.css" rel="stylesheet" nonce="..." />
|
|
193
207
|
def stylesheet_link_tag(*sources)
|
|
194
208
|
options = sources.extract_options!.stringify_keys
|
|
195
209
|
path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
|
|
@@ -197,7 +211,7 @@ module ActionView
|
|
|
197
211
|
preload_links = []
|
|
198
212
|
crossorigin = options.delete("crossorigin")
|
|
199
213
|
crossorigin = "anonymous" if crossorigin == true
|
|
200
|
-
nopush = options["nopush"].nil?
|
|
214
|
+
nopush = options["nopush"].nil? || options.delete("nopush")
|
|
201
215
|
integrity = options["integrity"]
|
|
202
216
|
|
|
203
217
|
sources_tags = sources.uniq.map { |source|
|
|
@@ -206,6 +220,7 @@ module ActionView
|
|
|
206
220
|
preload_link = "<#{href}>; rel=preload; as=style"
|
|
207
221
|
preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil?
|
|
208
222
|
preload_link += "; integrity=#{integrity}" unless integrity.nil?
|
|
223
|
+
preload_link += "; nonce=#{content_security_policy_nonce}" if options["nonce"] == true
|
|
209
224
|
preload_link += "; nopush" if nopush
|
|
210
225
|
preload_links << preload_link
|
|
211
226
|
end
|
|
@@ -214,6 +229,11 @@ module ActionView
|
|
|
214
229
|
"crossorigin" => crossorigin,
|
|
215
230
|
"href" => href
|
|
216
231
|
}.merge!(options)
|
|
232
|
+
if tag_options["nonce"] == true || (!tag_options.key?("nonce") && auto_include_nonce_for_styles && respond_to?(:content_security_policy_nonce))
|
|
233
|
+
tag_options["nonce"] = content_security_policy_nonce
|
|
234
|
+
elsif tag_options["nonce"] == false
|
|
235
|
+
tag_options.delete("nonce")
|
|
236
|
+
end
|
|
217
237
|
|
|
218
238
|
if apply_stylesheet_media_default && tag_options["media"].blank?
|
|
219
239
|
tag_options["media"] = "screen"
|
|
@@ -348,21 +368,32 @@ module ActionView
|
|
|
348
368
|
crossorigin = options.delete(:crossorigin)
|
|
349
369
|
crossorigin = "anonymous" if crossorigin == true || (crossorigin.blank? && as_type == "font")
|
|
350
370
|
integrity = options[:integrity]
|
|
371
|
+
fetchpriority = options.delete(:fetchpriority)
|
|
351
372
|
nopush = options.delete(:nopush) || false
|
|
352
|
-
rel = mime_type == "module" ? "modulepreload" : "preload"
|
|
373
|
+
rel = mime_type == "module" || mime_type == :module ? "modulepreload" : "preload"
|
|
374
|
+
add_nonce = content_security_policy_nonce &&
|
|
375
|
+
respond_to?(:request) &&
|
|
376
|
+
request.content_security_policy_nonce_directives&.include?("#{as_type}-src")
|
|
377
|
+
|
|
378
|
+
if add_nonce
|
|
379
|
+
options[:nonce] = content_security_policy_nonce
|
|
380
|
+
end
|
|
353
381
|
|
|
354
|
-
link_tag = tag.link(
|
|
382
|
+
link_tag = tag.link(
|
|
355
383
|
rel: rel,
|
|
356
384
|
href: href,
|
|
357
385
|
as: as_type,
|
|
358
386
|
type: mime_type,
|
|
359
|
-
crossorigin: crossorigin
|
|
360
|
-
|
|
387
|
+
crossorigin: crossorigin,
|
|
388
|
+
fetchpriority: fetchpriority,
|
|
389
|
+
**options.symbolize_keys)
|
|
361
390
|
|
|
362
391
|
preload_link = "<#{href}>; rel=#{rel}; as=#{as_type}"
|
|
363
392
|
preload_link += "; type=#{mime_type}" if mime_type
|
|
364
393
|
preload_link += "; crossorigin=#{crossorigin}" if crossorigin
|
|
394
|
+
preload_link += "; fetchpriority=#{fetchpriority}" if fetchpriority
|
|
365
395
|
preload_link += "; integrity=#{integrity}" if integrity
|
|
396
|
+
preload_link += "; nonce=#{content_security_policy_nonce}" if add_nonce
|
|
366
397
|
preload_link += "; nopush" if nopush
|
|
367
398
|
|
|
368
399
|
send_preload_links_header([preload_link])
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "set"
|
|
4
|
-
|
|
5
3
|
module ActionView
|
|
6
4
|
module Helpers # :nodoc:
|
|
7
5
|
# = Action View Atom Feed \Helpers
|
|
@@ -172,7 +170,7 @@ module ActionView
|
|
|
172
170
|
|
|
173
171
|
# Creates an entry tag for a specific record and prefills the id using class and id.
|
|
174
172
|
#
|
|
175
|
-
# Options
|
|
173
|
+
# ==== Options
|
|
176
174
|
#
|
|
177
175
|
# * <tt>:published</tt>: Time first published. Defaults to the created_at attribute on the record if one such exists.
|
|
178
176
|
# * <tt>:updated</tt>: Time of update. Defaults to the updated_at attribute on the record if one such exists.
|
|
@@ -93,6 +93,14 @@ module ActionView
|
|
|
93
93
|
# render partial: 'attachments/attachment', collection: group_of_attachments
|
|
94
94
|
# render partial: 'documents/document', collection: @project.documents.where(published: true).order('created_at')
|
|
95
95
|
#
|
|
96
|
+
# One last type of dependency can be determined implicitly:
|
|
97
|
+
#
|
|
98
|
+
# render "maintenance_tasks/runs/info/#{run.status}"
|
|
99
|
+
#
|
|
100
|
+
# Because the value passed to render ends in interpolation, Action View
|
|
101
|
+
# will mark all partials within the "maintenance_tasks/runs/info" folder as
|
|
102
|
+
# dependencies.
|
|
103
|
+
#
|
|
96
104
|
# === Explicit dependencies
|
|
97
105
|
#
|
|
98
106
|
# Sometimes you'll have template dependencies that can't be derived at all. This is typically
|
|
@@ -189,7 +197,7 @@ module ActionView
|
|
|
189
197
|
CachingRegistry.caching?
|
|
190
198
|
end
|
|
191
199
|
|
|
192
|
-
# Raises
|
|
200
|
+
# Raises UncacheableFragmentError when called from within a +cache+ block.
|
|
193
201
|
#
|
|
194
202
|
# Useful to denote helper methods that can't participate in fragment caching:
|
|
195
203
|
#
|
|
@@ -198,7 +206,7 @@ module ActionView
|
|
|
198
206
|
# "#{project.name} - #{Time.now}"
|
|
199
207
|
# end
|
|
200
208
|
#
|
|
201
|
-
# # Which will then raise if used within a
|
|
209
|
+
# # Which will then raise if used within a `cache` block:
|
|
202
210
|
# <% cache project do %>
|
|
203
211
|
# <%= project_name_with_time(project) %>
|
|
204
212
|
# <% end %>
|
|
@@ -44,10 +44,10 @@ module ActionView
|
|
|
44
44
|
#
|
|
45
45
|
# @greeting # => "Welcome to my shiny new web page! The date and time is 2018-09-06 11:09:16 -0500"
|
|
46
46
|
#
|
|
47
|
-
def capture(
|
|
47
|
+
def capture(*, **, &block)
|
|
48
48
|
value = nil
|
|
49
49
|
@output_buffer ||= ActionView::OutputBuffer.new
|
|
50
|
-
buffer = @output_buffer.capture { value = yield(
|
|
50
|
+
buffer = @output_buffer.capture { value = yield(*, **) }
|
|
51
51
|
|
|
52
52
|
string = if @output_buffer.equal?(value)
|
|
53
53
|
buffer
|
|
@@ -20,11 +20,15 @@ module ActionView
|
|
|
20
20
|
def assign_controller(controller)
|
|
21
21
|
if @_controller = controller
|
|
22
22
|
@_request = controller.request if controller.respond_to?(:request)
|
|
23
|
-
|
|
23
|
+
if controller.respond_to?(:config)
|
|
24
|
+
@_config = controller.config.inheritable_copy
|
|
25
|
+
else
|
|
26
|
+
@_config = ActiveSupport::InheritableOptions.new
|
|
27
|
+
end
|
|
24
28
|
@_default_form_builder = controller.default_form_builder if controller.respond_to?(:default_form_builder)
|
|
25
29
|
else
|
|
26
30
|
@_request ||= nil
|
|
27
|
-
@_config
|
|
31
|
+
@_config = ActiveSupport::InheritableOptions.new
|
|
28
32
|
@_default_form_builder ||= nil
|
|
29
33
|
end
|
|
30
34
|
end
|
|
@@ -17,7 +17,7 @@ module ActionView
|
|
|
17
17
|
# You don't need to use these tags for regular forms as they generate their own hidden fields.
|
|
18
18
|
#
|
|
19
19
|
# For Ajax requests other than GETs, extract the "csrf-token" from the meta-tag and send as the
|
|
20
|
-
# +X-CSRF-Token+ HTTP header.
|
|
20
|
+
# +X-CSRF-Token+ HTTP header.
|
|
21
21
|
#
|
|
22
22
|
def csrf_meta_tags
|
|
23
23
|
if defined?(protect_against_forgery?) && protect_against_forgery?
|
|
@@ -136,8 +136,15 @@ module ActionView
|
|
|
136
136
|
from_year += 1 if from_time.month >= 3
|
|
137
137
|
to_year = to_time.year
|
|
138
138
|
to_year -= 1 if to_time.month < 3
|
|
139
|
-
|
|
139
|
+
|
|
140
|
+
leap_years = if from_year > to_year
|
|
141
|
+
0
|
|
142
|
+
else
|
|
143
|
+
fyear = from_year - 1
|
|
144
|
+
(to_year / 4 - to_year / 100 + to_year / 400) - (fyear / 4 - fyear / 100 + fyear / 400)
|
|
145
|
+
end
|
|
140
146
|
minute_offset_for_leap_year = leap_years * 1440
|
|
147
|
+
|
|
141
148
|
# Discount the leap year days when calculating year distance.
|
|
142
149
|
# e.g. if there are 20 leap year days between 2 dates having the same day
|
|
143
150
|
# and month then based on 365 days calculation
|
|
@@ -179,6 +186,23 @@ module ActionView
|
|
|
179
186
|
|
|
180
187
|
alias_method :distance_of_time_in_words_to_now, :time_ago_in_words
|
|
181
188
|
|
|
189
|
+
# Like <tt>time_ago_in_words</tt>, but adds a prefix/suffix depending on whether the time is in the past or future.
|
|
190
|
+
# You can use the <tt>scope</tt> option to customize the translation scope. All other options
|
|
191
|
+
# are forwarded to <tt>time_ago_in_words</tt>.
|
|
192
|
+
#
|
|
193
|
+
# relative_time_in_words(3.minutes.from_now) # => "in 3 minutes"
|
|
194
|
+
# relative_time_in_words(3.minutes.ago) # => "3 minutes ago"
|
|
195
|
+
# relative_time_in_words(10.seconds.ago, include_seconds: true) # => "less than 10 seconds ago"
|
|
196
|
+
#
|
|
197
|
+
# See also #time_ago_in_words
|
|
198
|
+
def relative_time_in_words(from_time, options = {})
|
|
199
|
+
now = Time.now
|
|
200
|
+
time = distance_of_time_in_words(from_time, now, options.except(:scope))
|
|
201
|
+
key = from_time > now ? :future : :past
|
|
202
|
+
|
|
203
|
+
I18n.t(key, time: time, scope: options.fetch(:scope, :'datetime.relative'), locale: options[:locale])
|
|
204
|
+
end
|
|
205
|
+
|
|
182
206
|
# Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based
|
|
183
207
|
# attribute (identified by +method+) on an object assigned to the template (identified by +object+).
|
|
184
208
|
#
|
|
@@ -1228,7 +1252,7 @@ module ActionView
|
|
|
1228
1252
|
class FormBuilder
|
|
1229
1253
|
# Wraps ActionView::Helpers::DateHelper#date_select for form builders:
|
|
1230
1254
|
#
|
|
1231
|
-
# <%=
|
|
1255
|
+
# <%= form_with model: @person do |f| %>
|
|
1232
1256
|
# <%= f.date_select :birth_date %>
|
|
1233
1257
|
# <%= f.submit %>
|
|
1234
1258
|
# <% end %>
|
|
@@ -1240,7 +1264,7 @@ module ActionView
|
|
|
1240
1264
|
|
|
1241
1265
|
# Wraps ActionView::Helpers::DateHelper#time_select for form builders:
|
|
1242
1266
|
#
|
|
1243
|
-
# <%=
|
|
1267
|
+
# <%= form_with model: @race do |f| %>
|
|
1244
1268
|
# <%= f.time_select :average_lap %>
|
|
1245
1269
|
# <%= f.submit %>
|
|
1246
1270
|
# <% end %>
|
|
@@ -1252,7 +1276,7 @@ module ActionView
|
|
|
1252
1276
|
|
|
1253
1277
|
# Wraps ActionView::Helpers::DateHelper#datetime_select for form builders:
|
|
1254
1278
|
#
|
|
1255
|
-
# <%=
|
|
1279
|
+
# <%= form_with model: @person do |f| %>
|
|
1256
1280
|
# <%= f.datetime_select :last_request_at %>
|
|
1257
1281
|
# <%= f.submit %>
|
|
1258
1282
|
# <% end %>
|