omg-actionview 8.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +25 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +40 -0
  5. data/app/assets/javascripts/rails-ujs.esm.js +686 -0
  6. data/app/assets/javascripts/rails-ujs.js +630 -0
  7. data/lib/action_view/base.rb +316 -0
  8. data/lib/action_view/buffers.rb +165 -0
  9. data/lib/action_view/cache_expiry.rb +69 -0
  10. data/lib/action_view/context.rb +32 -0
  11. data/lib/action_view/dependency_tracker/erb_tracker.rb +159 -0
  12. data/lib/action_view/dependency_tracker/ruby_tracker.rb +43 -0
  13. data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
  14. data/lib/action_view/dependency_tracker.rb +41 -0
  15. data/lib/action_view/deprecator.rb +7 -0
  16. data/lib/action_view/digestor.rb +130 -0
  17. data/lib/action_view/flows.rb +75 -0
  18. data/lib/action_view/gem_version.rb +17 -0
  19. data/lib/action_view/helpers/active_model_helper.rb +54 -0
  20. data/lib/action_view/helpers/asset_tag_helper.rb +680 -0
  21. data/lib/action_view/helpers/asset_url_helper.rb +473 -0
  22. data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
  23. data/lib/action_view/helpers/cache_helper.rb +315 -0
  24. data/lib/action_view/helpers/capture_helper.rb +236 -0
  25. data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
  26. data/lib/action_view/helpers/controller_helper.rb +42 -0
  27. data/lib/action_view/helpers/csp_helper.rb +26 -0
  28. data/lib/action_view/helpers/csrf_helper.rb +35 -0
  29. data/lib/action_view/helpers/date_helper.rb +1266 -0
  30. data/lib/action_view/helpers/debug_helper.rb +38 -0
  31. data/lib/action_view/helpers/form_helper.rb +2765 -0
  32. data/lib/action_view/helpers/form_options_helper.rb +927 -0
  33. data/lib/action_view/helpers/form_tag_helper.rb +1088 -0
  34. data/lib/action_view/helpers/javascript_helper.rb +96 -0
  35. data/lib/action_view/helpers/number_helper.rb +165 -0
  36. data/lib/action_view/helpers/output_safety_helper.rb +70 -0
  37. data/lib/action_view/helpers/rendering_helper.rb +218 -0
  38. data/lib/action_view/helpers/sanitize_helper.rb +201 -0
  39. data/lib/action_view/helpers/tag_helper.rb +621 -0
  40. data/lib/action_view/helpers/tags/base.rb +138 -0
  41. data/lib/action_view/helpers/tags/check_box.rb +65 -0
  42. data/lib/action_view/helpers/tags/checkable.rb +18 -0
  43. data/lib/action_view/helpers/tags/collection_check_boxes.rb +37 -0
  44. data/lib/action_view/helpers/tags/collection_helpers.rb +118 -0
  45. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
  46. data/lib/action_view/helpers/tags/collection_select.rb +33 -0
  47. data/lib/action_view/helpers/tags/color_field.rb +26 -0
  48. data/lib/action_view/helpers/tags/date_field.rb +14 -0
  49. data/lib/action_view/helpers/tags/date_select.rb +75 -0
  50. data/lib/action_view/helpers/tags/datetime_field.rb +39 -0
  51. data/lib/action_view/helpers/tags/datetime_local_field.rb +29 -0
  52. data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
  53. data/lib/action_view/helpers/tags/email_field.rb +10 -0
  54. data/lib/action_view/helpers/tags/file_field.rb +26 -0
  55. data/lib/action_view/helpers/tags/grouped_collection_select.rb +34 -0
  56. data/lib/action_view/helpers/tags/hidden_field.rb +14 -0
  57. data/lib/action_view/helpers/tags/label.rb +84 -0
  58. data/lib/action_view/helpers/tags/month_field.rb +14 -0
  59. data/lib/action_view/helpers/tags/number_field.rb +20 -0
  60. data/lib/action_view/helpers/tags/password_field.rb +14 -0
  61. data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
  62. data/lib/action_view/helpers/tags/radio_button.rb +32 -0
  63. data/lib/action_view/helpers/tags/range_field.rb +10 -0
  64. data/lib/action_view/helpers/tags/search_field.rb +27 -0
  65. data/lib/action_view/helpers/tags/select.rb +45 -0
  66. data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
  67. data/lib/action_view/helpers/tags/tel_field.rb +10 -0
  68. data/lib/action_view/helpers/tags/text_area.rb +24 -0
  69. data/lib/action_view/helpers/tags/text_field.rb +33 -0
  70. data/lib/action_view/helpers/tags/time_field.rb +23 -0
  71. data/lib/action_view/helpers/tags/time_select.rb +10 -0
  72. data/lib/action_view/helpers/tags/time_zone_select.rb +25 -0
  73. data/lib/action_view/helpers/tags/translator.rb +39 -0
  74. data/lib/action_view/helpers/tags/url_field.rb +10 -0
  75. data/lib/action_view/helpers/tags/week_field.rb +14 -0
  76. data/lib/action_view/helpers/tags/weekday_select.rb +31 -0
  77. data/lib/action_view/helpers/tags.rb +47 -0
  78. data/lib/action_view/helpers/text_helper.rb +568 -0
  79. data/lib/action_view/helpers/translation_helper.rb +161 -0
  80. data/lib/action_view/helpers/url_helper.rb +812 -0
  81. data/lib/action_view/helpers.rb +68 -0
  82. data/lib/action_view/layouts.rb +434 -0
  83. data/lib/action_view/locale/en.yml +56 -0
  84. data/lib/action_view/log_subscriber.rb +132 -0
  85. data/lib/action_view/lookup_context.rb +299 -0
  86. data/lib/action_view/model_naming.rb +14 -0
  87. data/lib/action_view/path_registry.rb +57 -0
  88. data/lib/action_view/path_set.rb +84 -0
  89. data/lib/action_view/railtie.rb +132 -0
  90. data/lib/action_view/record_identifier.rb +118 -0
  91. data/lib/action_view/render_parser/prism_render_parser.rb +139 -0
  92. data/lib/action_view/render_parser/ripper_render_parser.rb +350 -0
  93. data/lib/action_view/render_parser.rb +40 -0
  94. data/lib/action_view/renderer/abstract_renderer.rb +186 -0
  95. data/lib/action_view/renderer/collection_renderer.rb +204 -0
  96. data/lib/action_view/renderer/object_renderer.rb +34 -0
  97. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +120 -0
  98. data/lib/action_view/renderer/partial_renderer.rb +267 -0
  99. data/lib/action_view/renderer/renderer.rb +107 -0
  100. data/lib/action_view/renderer/streaming_template_renderer.rb +107 -0
  101. data/lib/action_view/renderer/template_renderer.rb +115 -0
  102. data/lib/action_view/rendering.rb +190 -0
  103. data/lib/action_view/routing_url_for.rb +149 -0
  104. data/lib/action_view/tasks/cache_digests.rake +25 -0
  105. data/lib/action_view/template/error.rb +264 -0
  106. data/lib/action_view/template/handlers/builder.rb +25 -0
  107. data/lib/action_view/template/handlers/erb/erubi.rb +85 -0
  108. data/lib/action_view/template/handlers/erb.rb +157 -0
  109. data/lib/action_view/template/handlers/html.rb +11 -0
  110. data/lib/action_view/template/handlers/raw.rb +11 -0
  111. data/lib/action_view/template/handlers.rb +66 -0
  112. data/lib/action_view/template/html.rb +33 -0
  113. data/lib/action_view/template/inline.rb +22 -0
  114. data/lib/action_view/template/raw_file.rb +25 -0
  115. data/lib/action_view/template/renderable.rb +30 -0
  116. data/lib/action_view/template/resolver.rb +212 -0
  117. data/lib/action_view/template/sources/file.rb +17 -0
  118. data/lib/action_view/template/sources.rb +13 -0
  119. data/lib/action_view/template/text.rb +32 -0
  120. data/lib/action_view/template/types.rb +50 -0
  121. data/lib/action_view/template.rb +580 -0
  122. data/lib/action_view/template_details.rb +66 -0
  123. data/lib/action_view/template_path.rb +66 -0
  124. data/lib/action_view/test_case.rb +449 -0
  125. data/lib/action_view/testing/resolvers.rb +44 -0
  126. data/lib/action_view/unbound_template.rb +67 -0
  127. data/lib/action_view/version.rb +10 -0
  128. data/lib/action_view/view_paths.rb +117 -0
  129. data/lib/action_view.rb +104 -0
  130. metadata +275 -0
@@ -0,0 +1,812 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/array/access"
4
+ require "active_support/core_ext/hash/keys"
5
+ require "active_support/core_ext/string/output_safety"
6
+ require "action_view/helpers/content_exfiltration_prevention_helper"
7
+ require "action_view/helpers/tag_helper"
8
+
9
+ module ActionView
10
+ module Helpers # :nodoc:
11
+ # = Action View URL \Helpers
12
+ #
13
+ # Provides a set of methods for making links and getting URLs that
14
+ # depend on the routing subsystem (see ActionDispatch::Routing).
15
+ # This allows you to use the same format for links in views
16
+ # and controllers.
17
+ module UrlHelper
18
+ # This helper may be included in any class that includes the
19
+ # URL helpers of a routes (routes.url_helpers). Some methods
20
+ # provided here will only work in the context of a request
21
+ # (link_to_unless_current, for instance), which must be provided
22
+ # as a method called #request on the context.
23
+ BUTTON_TAG_METHOD_VERBS = %w{patch put delete}
24
+ extend ActiveSupport::Concern
25
+
26
+ include TagHelper
27
+ include ContentExfiltrationPreventionHelper
28
+
29
+ module ClassMethods
30
+ def _url_for_modules
31
+ ActionView::RoutingUrlFor
32
+ end
33
+ end
34
+
35
+ mattr_accessor :button_to_generates_button_tag, default: false
36
+
37
+ # Basic implementation of url_for to allow use helpers without routes existence
38
+ def url_for(options = nil) # :nodoc:
39
+ case options
40
+ when String
41
+ options
42
+ when :back
43
+ _back_url
44
+ else
45
+ raise ArgumentError, "arguments passed to url_for can't be handled. Please require " \
46
+ "routes or provide your own implementation"
47
+ end
48
+ end
49
+
50
+ def _back_url # :nodoc:
51
+ _filtered_referrer || "javascript:history.back()"
52
+ end
53
+ private :_back_url
54
+
55
+ def _filtered_referrer # :nodoc:
56
+ if controller.respond_to?(:request)
57
+ referrer = controller.request.env["HTTP_REFERER"]
58
+ if referrer && URI(referrer).scheme != "javascript"
59
+ referrer
60
+ end
61
+ end
62
+ rescue URI::InvalidURIError
63
+ end
64
+ private :_filtered_referrer
65
+
66
+ # Creates an anchor element of the given +name+ using a URL created by the set of +options+.
67
+ # See the valid options in the documentation for +url_for+. It's also possible to
68
+ # pass a \String instead of an options hash, which generates an anchor element that uses the
69
+ # value of the \String as the href for the link. Using a <tt>:back</tt> \Symbol instead
70
+ # of an options hash will generate a link to the referrer (a JavaScript back link
71
+ # will be used in place of a referrer if none exists). If +nil+ is passed as the name
72
+ # the value of the link itself will become the name.
73
+ #
74
+ # ==== Signatures
75
+ #
76
+ # link_to(body, url, html_options = {})
77
+ # # url is a String; you can use URL helpers like
78
+ # # posts_path
79
+ #
80
+ # link_to(body, url_options = {}, html_options = {})
81
+ # # url_options, except :method, is passed to url_for
82
+ #
83
+ # link_to(options = {}, html_options = {}) do
84
+ # # name
85
+ # end
86
+ #
87
+ # link_to(url, html_options = {}) do
88
+ # # name
89
+ # end
90
+ #
91
+ # link_to(active_record_model)
92
+ #
93
+ # ==== Options
94
+ # * <tt>:data</tt> - This option can be used to add custom data attributes.
95
+ #
96
+ # ==== Examples
97
+ #
98
+ # Because it relies on +url_for+, +link_to+ supports both older-style controller/action/id arguments
99
+ # and newer RESTful routes. Current \Rails style favors RESTful routes whenever possible, so base
100
+ # your application on resources and use
101
+ #
102
+ # link_to "Profile", profile_path(@profile)
103
+ # # => <a href="/profiles/1">Profile</a>
104
+ #
105
+ # or the even pithier
106
+ #
107
+ # link_to "Profile", @profile
108
+ # # => <a href="/profiles/1">Profile</a>
109
+ #
110
+ # in place of the older more verbose, non-resource-oriented
111
+ #
112
+ # link_to "Profile", controller: "profiles", action: "show", id: @profile
113
+ # # => <a href="/profiles/show/1">Profile</a>
114
+ #
115
+ # Similarly,
116
+ #
117
+ # link_to "Profiles", profiles_path
118
+ # # => <a href="/profiles">Profiles</a>
119
+ #
120
+ # is better than
121
+ #
122
+ # link_to "Profiles", controller: "profiles"
123
+ # # => <a href="/profiles">Profiles</a>
124
+ #
125
+ # When name is +nil+ the href is presented instead
126
+ #
127
+ # link_to nil, "http://example.com"
128
+ # # => <a href="http://www.example.com">http://www.example.com</a>
129
+ #
130
+ # More concise yet, when +name+ is an Active Record model that defines a
131
+ # +to_s+ method returning a default value or a model instance attribute
132
+ #
133
+ # link_to @profile
134
+ # # => <a href="http://www.example.com/profiles/1">Eileen</a>
135
+ #
136
+ # You can use a block as well if your link target is hard to fit into the name parameter. ERB example:
137
+ #
138
+ # <%= link_to(@profile) do %>
139
+ # <strong><%= @profile.name %></strong> -- <span>Check it out!</span>
140
+ # <% end %>
141
+ # # => <a href="/profiles/1">
142
+ # <strong>David</strong> -- <span>Check it out!</span>
143
+ # </a>
144
+ #
145
+ # Classes and ids for CSS are easy to produce:
146
+ #
147
+ # link_to "Articles", articles_path, id: "news", class: "article"
148
+ # # => <a href="/articles" class="article" id="news">Articles</a>
149
+ #
150
+ # Be careful when using the older argument style, as an extra literal hash is needed:
151
+ #
152
+ # link_to "Articles", { controller: "articles" }, id: "news", class: "article"
153
+ # # => <a href="/articles" class="article" id="news">Articles</a>
154
+ #
155
+ # Leaving the hash off gives the wrong link:
156
+ #
157
+ # link_to "WRONG!", controller: "articles", id: "news", class: "article"
158
+ # # => <a href="/articles/index/news?class=article">WRONG!</a>
159
+ #
160
+ # +link_to+ can also produce links with anchors or query strings:
161
+ #
162
+ # link_to "Comment wall", profile_path(@profile, anchor: "wall")
163
+ # # => <a href="/profiles/1#wall">Comment wall</a>
164
+ #
165
+ # link_to "Ruby on Rails search", controller: "searches", query: "ruby on rails"
166
+ # # => <a href="/searches?query=ruby+on+rails">Ruby on Rails search</a>
167
+ #
168
+ # link_to "Nonsense search", searches_path(foo: "bar", baz: "quux")
169
+ # # => <a href="/searches?foo=bar&baz=quux">Nonsense search</a>
170
+ #
171
+ # You can set any link attributes such as <tt>target</tt>, <tt>rel</tt>, <tt>type</tt>:
172
+ #
173
+ # link_to "External link", "http://www.rubyonrails.org/", target: "_blank", rel: "nofollow"
174
+ # # => <a href="http://www.rubyonrails.org/" target="_blank" rel="nofollow">External link</a>
175
+ #
176
+ # ==== Turbo
177
+ #
178
+ # Rails 7 ships with Turbo enabled by default. Turbo provides the following +:data+ options:
179
+ #
180
+ # * <tt>turbo_method: symbol of HTTP verb</tt> - Performs a Turbo link visit
181
+ # with the given HTTP verb. Forms are recommended when performing non-+GET+ requests.
182
+ # Only use <tt>data-turbo-method</tt> where a form is not possible.
183
+ #
184
+ # * <tt>turbo_confirm: "question?"</tt> - Adds a confirmation dialog to the link with the
185
+ # given value.
186
+ #
187
+ # {Consult the Turbo Handbook for more information on the options
188
+ # above.}[https://turbo.hotwired.dev/handbook/drive#performing-visits-with-a-different-method]
189
+ #
190
+ # ===== \Examples
191
+ #
192
+ # link_to "Delete profile", @profile, data: { turbo_method: :delete }
193
+ # # => <a href="/profiles/1" data-turbo-method="delete">Delete profile</a>
194
+ #
195
+ # link_to "Visit Other Site", "https://rubyonrails.org/", data: { turbo_confirm: "Are you sure?" }
196
+ # # => <a href="https://rubyonrails.org/" data-turbo-confirm="Are you sure?">Visit Other Site</a>
197
+ #
198
+ def link_to(name = nil, options = nil, html_options = nil, &block)
199
+ html_options, options, name = options, name, block if block_given?
200
+ options ||= {}
201
+
202
+ html_options = convert_options_to_data_attributes(options, html_options)
203
+
204
+ url = url_target(name, options)
205
+ html_options["href"] ||= url
206
+
207
+ content_tag("a", name || url, html_options, &block)
208
+ end
209
+
210
+ # Generates a form containing a single button that submits to the URL created
211
+ # by the set of +options+. This is the safest method to ensure links that
212
+ # cause changes to your data are not triggered by search bots or accelerators.
213
+ #
214
+ # You can control the form and button behavior with +html_options+. Most
215
+ # values in +html_options+ are passed through to the button element. For
216
+ # example, passing a +:class+ option within +html_options+ will set the
217
+ # class attribute of the button element.
218
+ #
219
+ # The class attribute of the form element can be set by passing a
220
+ # +:form_class+ option within +html_options+. It defaults to
221
+ # <tt>"button_to"</tt> to allow styling of the form and its children.
222
+ #
223
+ # The form submits a POST request by default if the object is not persisted;
224
+ # conversely, if the object is persisted, it will submit a PATCH request.
225
+ # To specify a different HTTP verb use the +:method+ option within +html_options+.
226
+ #
227
+ # If the HTML button generated from +button_to+ does not work with your layout, you can
228
+ # consider using the +link_to+ method with the +data-turbo-method+
229
+ # attribute as described in the +link_to+ documentation.
230
+ #
231
+ # ==== Options
232
+ # The +options+ hash accepts the same options as +url_for+. To generate a
233
+ # <tt><form></tt> element without an <tt>[action]</tt> attribute, pass
234
+ # <tt>false</tt>:
235
+ #
236
+ # <%= button_to "New", false %>
237
+ # # => "<form method="post" class="button_to">
238
+ # # <button type="submit">New</button>
239
+ # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
240
+ # # </form>"
241
+ #
242
+ # Most values in +html_options+ are passed through to the button element,
243
+ # but there are a few special options:
244
+ #
245
+ # * <tt>:method</tt> - \Symbol of HTTP verb. Supported verbs are <tt>:post</tt>, <tt>:get</tt>,
246
+ # <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>. By default it will be <tt>:post</tt>.
247
+ # * <tt>:disabled</tt> - If set to true, it will generate a disabled button.
248
+ # * <tt>:data</tt> - This option can be used to add custom data attributes.
249
+ # * <tt>:form</tt> - This hash will be form attributes
250
+ # * <tt>:form_class</tt> - This controls the class of the form within which the submit button will
251
+ # be placed
252
+ # * <tt>:params</tt> - \Hash of parameters to be rendered as hidden fields within the form.
253
+ #
254
+ # ==== Examples
255
+ # <%= button_to "New", action: "new" %>
256
+ # # => "<form method="post" action="/controller/new" class="button_to">
257
+ # # <button type="submit">New</button>
258
+ # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6" autocomplete="off"/>
259
+ # # </form>"
260
+ #
261
+ # <%= button_to "New", new_article_path %>
262
+ # # => "<form method="post" action="/articles/new" class="button_to">
263
+ # # <button type="submit">New</button>
264
+ # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6" autocomplete="off"/>
265
+ # # </form>"
266
+ #
267
+ # <%= button_to "New", new_article_path, params: { time: Time.now } %>
268
+ # # => "<form method="post" action="/articles/new" class="button_to">
269
+ # # <button type="submit">New</button>
270
+ # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
271
+ # # <input type="hidden" name="time" value="2021-04-08 14:06:09 -0500" autocomplete="off">
272
+ # # </form>"
273
+ #
274
+ # <%= button_to [:make_happy, @user] do %>
275
+ # Make happy <strong><%= @user.name %></strong>
276
+ # <% end %>
277
+ # # => "<form method="post" action="/users/1/make_happy" class="button_to">
278
+ # # <button type="submit">
279
+ # # Make happy <strong><%= @user.name %></strong>
280
+ # # </button>
281
+ # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6" autocomplete="off"/>
282
+ # # </form>"
283
+ #
284
+ # <%= button_to "New", { action: "new" }, form_class: "new-thing" %>
285
+ # # => "<form method="post" action="/controller/new" class="new-thing">
286
+ # # <button type="submit">New</button>
287
+ # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6" autocomplete="off"/>
288
+ # # </form>"
289
+ #
290
+ # <%= button_to "Create", { action: "create" }, form: { "data-type" => "json" } %>
291
+ # # => "<form method="post" action="/images/create" class="button_to" data-type="json">
292
+ # # <button type="submit">Create</button>
293
+ # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6" autocomplete="off"/>
294
+ # # </form>"
295
+ #
296
+ def button_to(name = nil, options = nil, html_options = nil, &block)
297
+ html_options, options = options, name if block_given?
298
+ html_options ||= {}
299
+ html_options = html_options.stringify_keys
300
+
301
+ url =
302
+ case options
303
+ when FalseClass then nil
304
+ else url_for(options)
305
+ end
306
+
307
+ remote = html_options.delete("remote")
308
+ params = html_options.delete("params")
309
+
310
+ authenticity_token = html_options.delete("authenticity_token")
311
+
312
+ method = (html_options.delete("method").presence || method_for_options(options)).to_s
313
+ method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : "".html_safe
314
+
315
+ form_method = method == "get" ? "get" : "post"
316
+ form_options = html_options.delete("form") || {}
317
+ form_options[:class] ||= html_options.delete("form_class") || "button_to"
318
+ form_options[:method] = form_method
319
+ form_options[:action] = url
320
+ form_options[:'data-remote'] = true if remote
321
+
322
+ request_token_tag = if form_method == "post"
323
+ request_method = method.empty? ? "post" : method
324
+ token_tag(authenticity_token, form_options: { action: url, method: request_method })
325
+ else
326
+ ""
327
+ end
328
+
329
+ html_options = convert_options_to_data_attributes(options, html_options)
330
+ html_options["type"] = "submit"
331
+
332
+ button = if block_given?
333
+ content_tag("button", html_options, &block)
334
+ elsif button_to_generates_button_tag
335
+ content_tag("button", name || url, html_options, &block)
336
+ else
337
+ html_options["value"] = name || url
338
+ tag("input", html_options)
339
+ end
340
+
341
+ inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag)
342
+ if params
343
+ to_form_params(params).each do |param|
344
+ inner_tags.safe_concat tag(:input, type: "hidden", name: param[:name], value: param[:value],
345
+ autocomplete: "off")
346
+ end
347
+ end
348
+ html = content_tag("form", inner_tags, form_options)
349
+ prevent_content_exfiltration(html)
350
+ end
351
+
352
+ # Creates a link tag of the given +name+ using a URL created by the set of
353
+ # +options+ unless the current request URI is the same as the links, in
354
+ # which case only the name is returned (or the given block is yielded, if
355
+ # one exists). You can give +link_to_unless_current+ a block which will
356
+ # specialize the default behavior (e.g., show a "Start Here" link rather
357
+ # than the link's text).
358
+ #
359
+ # ==== Examples
360
+ # Let's say you have a navigation menu...
361
+ #
362
+ # <ul id="navbar">
363
+ # <li><%= link_to_unless_current("Home", { action: "index" }) %></li>
364
+ # <li><%= link_to_unless_current("About Us", { action: "about" }) %></li>
365
+ # </ul>
366
+ #
367
+ # If in the "about" action, it will render...
368
+ #
369
+ # <ul id="navbar">
370
+ # <li><a href="/controller/index">Home</a></li>
371
+ # <li>About Us</li>
372
+ # </ul>
373
+ #
374
+ # ...but if in the "index" action, it will render:
375
+ #
376
+ # <ul id="navbar">
377
+ # <li>Home</li>
378
+ # <li><a href="/controller/about">About Us</a></li>
379
+ # </ul>
380
+ #
381
+ # The implicit block given to +link_to_unless_current+ is evaluated if the current
382
+ # action is the action given. So, if we had a comments page and wanted to render a
383
+ # "Go Back" link instead of a link to the comments page, we could do something like this...
384
+ #
385
+ # <%=
386
+ # link_to_unless_current("Comment", { controller: "comments", action: "new" }) do
387
+ # link_to("Go back", { controller: "posts", action: "index" })
388
+ # end
389
+ # %>
390
+ def link_to_unless_current(name, options = {}, html_options = {}, &block)
391
+ link_to_unless current_page?(options), name, options, html_options, &block
392
+ end
393
+
394
+ # Creates a link tag of the given +name+ using a URL created by the set of
395
+ # +options+ unless +condition+ is true, in which case only the name is
396
+ # returned. To specialize the default behavior (i.e., show a login link rather
397
+ # than just the plaintext link text), you can pass a block that
398
+ # accepts the name or the full argument list for +link_to_unless+.
399
+ #
400
+ # ==== Examples
401
+ # <%= link_to_unless(@current_user.nil?, "Reply", { action: "reply" }) %>
402
+ # # If the user is logged in...
403
+ # # => <a href="/controller/reply/">Reply</a>
404
+ #
405
+ # <%=
406
+ # link_to_unless(@current_user.nil?, "Reply", { action: "reply" }) do |name|
407
+ # link_to(name, { controller: "accounts", action: "signup" })
408
+ # end
409
+ # %>
410
+ # # If the user is logged in...
411
+ # # => <a href="/controller/reply/">Reply</a>
412
+ # # If not...
413
+ # # => <a href="/accounts/signup">Reply</a>
414
+ def link_to_unless(condition, name, options = {}, html_options = {}, &block)
415
+ link_to_if !condition, name, options, html_options, &block
416
+ end
417
+
418
+ # Creates a link tag of the given +name+ using a URL created by the set of
419
+ # +options+ if +condition+ is true, otherwise only the name is
420
+ # returned. To specialize the default behavior, you can pass a block that
421
+ # accepts the name or the full argument list for +link_to_if+.
422
+ #
423
+ # ==== Examples
424
+ # <%= link_to_if(@current_user.nil?, "Login", { controller: "sessions", action: "new" }) %>
425
+ # # If the user isn't logged in...
426
+ # # => <a href="/sessions/new/">Login</a>
427
+ #
428
+ # <%=
429
+ # link_to_if(@current_user.nil?, "Login", { controller: "sessions", action: "new" }) do
430
+ # link_to(@current_user.login, { controller: "accounts", action: "show", id: @current_user })
431
+ # end
432
+ # %>
433
+ # # If the user isn't logged in...
434
+ # # => <a href="/sessions/new/">Login</a>
435
+ # # If they are logged in...
436
+ # # => <a href="/accounts/show/3">my_username</a>
437
+ def link_to_if(condition, name, options = {}, html_options = {}, &block)
438
+ if condition
439
+ link_to(name, options, html_options)
440
+ else
441
+ if block_given?
442
+ block.arity <= 1 ? capture(name, &block) : capture(name, options, html_options, &block)
443
+ else
444
+ ERB::Util.html_escape(name)
445
+ end
446
+ end
447
+ end
448
+
449
+ # Creates a mailto link tag to the specified +email_address+, which is
450
+ # also used as the name of the link unless +name+ is specified. Additional
451
+ # HTML attributes for the link can be passed in +html_options+.
452
+ #
453
+ # +mail_to+ has several methods for customizing the email itself by
454
+ # passing special keys to +html_options+.
455
+ #
456
+ # ==== Options
457
+ # * <tt>:subject</tt> - Preset the subject line of the email.
458
+ # * <tt>:body</tt> - Preset the body of the email.
459
+ # * <tt>:cc</tt> - Carbon Copy additional recipients on the email.
460
+ # * <tt>:bcc</tt> - Blind Carbon Copy additional recipients on the email.
461
+ # * <tt>:reply_to</tt> - Preset the +Reply-To+ field of the email.
462
+ #
463
+ # ==== Obfuscation
464
+ # Prior to \Rails 4.0, +mail_to+ provided options for encoding the address
465
+ # in order to hinder email harvesters. To take advantage of these options,
466
+ # install the +actionview-encoded_mail_to+ gem.
467
+ #
468
+ # ==== Examples
469
+ # mail_to "me@domain.com"
470
+ # # => <a href="mailto:me@domain.com">me@domain.com</a>
471
+ #
472
+ # mail_to "me@domain.com", "My email"
473
+ # # => <a href="mailto:me@domain.com">My email</a>
474
+ #
475
+ # mail_to "me@domain.com", cc: "ccaddress@domain.com",
476
+ # subject: "This is an example email"
477
+ # # => <a href="mailto:me@domain.com?cc=ccaddress@domain.com&subject=This%20is%20an%20example%20email">me@domain.com</a>
478
+ #
479
+ # You can use a block as well if your link target is hard to fit into the name parameter. ERB example:
480
+ #
481
+ # <%= mail_to "me@domain.com" do %>
482
+ # <strong>Email me:</strong> <span>me@domain.com</span>
483
+ # <% end %>
484
+ # # => <a href="mailto:me@domain.com">
485
+ # <strong>Email me:</strong> <span>me@domain.com</span>
486
+ # </a>
487
+ def mail_to(email_address, name = nil, html_options = {}, &block)
488
+ html_options, name = name, nil if name.is_a?(Hash)
489
+ html_options = (html_options || {}).stringify_keys
490
+
491
+ extras = %w{ cc bcc body subject reply_to }.map! { |item|
492
+ option = html_options.delete(item).presence || next
493
+ "#{item.dasherize}=#{ERB::Util.url_encode(option)}"
494
+ }.compact
495
+ extras = extras.empty? ? "" : "?" + extras.join("&")
496
+
497
+ encoded_email_address = ERB::Util.url_encode(email_address).gsub("%40", "@")
498
+ html_options["href"] = "mailto:#{encoded_email_address}#{extras}"
499
+
500
+ content_tag("a", name || email_address, html_options, &block)
501
+ end
502
+
503
+ # True if the current request URI was generated by the given +options+.
504
+ #
505
+ # ==== Examples
506
+ # Let's say we're in the <tt>http://www.example.com/shop/checkout?order=desc&page=1</tt> action.
507
+ #
508
+ # current_page?(action: 'process')
509
+ # # => false
510
+ #
511
+ # current_page?(action: 'checkout')
512
+ # # => true
513
+ #
514
+ # current_page?(controller: 'library', action: 'checkout')
515
+ # # => false
516
+ #
517
+ # current_page?(controller: 'shop', action: 'checkout')
518
+ # # => true
519
+ #
520
+ # current_page?(controller: 'shop', action: 'checkout', order: 'asc')
521
+ # # => false
522
+ #
523
+ # current_page?(controller: 'shop', action: 'checkout', order: 'desc', page: '1')
524
+ # # => true
525
+ #
526
+ # current_page?(controller: 'shop', action: 'checkout', order: 'desc', page: '2')
527
+ # # => false
528
+ #
529
+ # current_page?('http://www.example.com/shop/checkout')
530
+ # # => true
531
+ #
532
+ # current_page?('http://www.example.com/shop/checkout', check_parameters: true)
533
+ # # => false
534
+ #
535
+ # current_page?('/shop/checkout')
536
+ # # => true
537
+ #
538
+ # current_page?('http://www.example.com/shop/checkout?order=desc&page=1')
539
+ # # => true
540
+ #
541
+ # Let's say we're in the <tt>http://www.example.com/products</tt> action with method POST in case of invalid product.
542
+ #
543
+ # current_page?(controller: 'product', action: 'index')
544
+ # # => false
545
+ #
546
+ # We can also pass in the symbol arguments instead of strings.
547
+ #
548
+ def current_page?(options = nil, check_parameters: false, **options_as_kwargs)
549
+ unless request
550
+ raise "You cannot use helpers that need to determine the current " \
551
+ "page unless your view context provides a Request object " \
552
+ "in a #request method"
553
+ end
554
+
555
+ return false unless request.get? || request.head?
556
+
557
+ options ||= options_as_kwargs
558
+ check_parameters ||= options.is_a?(Hash) && options.delete(:check_parameters)
559
+ url_string = URI::RFC2396_PARSER.unescape(url_for(options)).force_encoding(Encoding::BINARY)
560
+
561
+ # We ignore any extra parameters in the request_uri if the
562
+ # submitted URL doesn't have any either. This lets the function
563
+ # work with things like ?order=asc
564
+ # the behavior can be disabled with check_parameters: true
565
+ request_uri = url_string.index("?") || check_parameters ? request.fullpath : request.path
566
+ request_uri = URI::RFC2396_PARSER.unescape(request_uri).force_encoding(Encoding::BINARY)
567
+
568
+ if %r{^\w+://}.match?(url_string)
569
+ request_uri = +"#{request.protocol}#{request.host_with_port}#{request_uri}"
570
+ end
571
+
572
+ remove_trailing_slash!(url_string)
573
+ remove_trailing_slash!(request_uri)
574
+
575
+ url_string == request_uri
576
+ end
577
+
578
+ # Creates an SMS anchor link tag to the specified +phone_number+. When the
579
+ # link is clicked, the default SMS messaging app is opened ready to send a
580
+ # message to the linked phone number. If the +body+ option is specified,
581
+ # the contents of the message will be preset to +body+.
582
+ #
583
+ # If +name+ is not specified, +phone_number+ will be used as the name of
584
+ # the link.
585
+ #
586
+ # A +country_code+ option is supported, which prepends a plus sign and the
587
+ # given country code to the linked phone number. For example,
588
+ # <tt>country_code: "01"</tt> will prepend <tt>+01</tt> to the linked
589
+ # phone number.
590
+ #
591
+ # Additional HTML attributes for the link can be passed via +html_options+.
592
+ #
593
+ # ==== Options
594
+ # * <tt>:country_code</tt> - Prepend the country code to the phone number.
595
+ # * <tt>:body</tt> - Preset the body of the message.
596
+ #
597
+ # ==== Examples
598
+ # sms_to "5155555785"
599
+ # # => <a href="sms:5155555785;">5155555785</a>
600
+ #
601
+ # sms_to "5155555785", country_code: "01"
602
+ # # => <a href="sms:+015155555785;">5155555785</a>
603
+ #
604
+ # sms_to "5155555785", "Text me"
605
+ # # => <a href="sms:5155555785;">Text me</a>
606
+ #
607
+ # sms_to "5155555785", body: "I have a question about your product."
608
+ # # => <a href="sms:5155555785;?body=I%20have%20a%20question%20about%20your%20product">5155555785</a>
609
+ #
610
+ # You can use a block as well if your link target is hard to fit into the name parameter. \ERB example:
611
+ #
612
+ # <%= sms_to "5155555785" do %>
613
+ # <strong>Text me:</strong>
614
+ # <% end %>
615
+ # # => <a href="sms:5155555785;">
616
+ # <strong>Text me:</strong>
617
+ # </a>
618
+ def sms_to(phone_number, name = nil, html_options = {}, &block)
619
+ html_options, name = name, nil if name.is_a?(Hash)
620
+ html_options = (html_options || {}).stringify_keys
621
+
622
+ country_code = html_options.delete("country_code").presence
623
+ country_code = country_code ? "+#{ERB::Util.url_encode(country_code)}" : ""
624
+
625
+ body = html_options.delete("body").presence
626
+ body = body ? "?&body=#{ERB::Util.url_encode(body)}" : ""
627
+
628
+ encoded_phone_number = ERB::Util.url_encode(phone_number)
629
+ html_options["href"] = "sms:#{country_code}#{encoded_phone_number};#{body}"
630
+
631
+ content_tag("a", name || phone_number, html_options, &block)
632
+ end
633
+
634
+ # Creates a TEL anchor link tag to the specified +phone_number+. When the
635
+ # link is clicked, the default app to make phone calls is opened and
636
+ # prepopulated with the phone number.
637
+ #
638
+ # If +name+ is not specified, +phone_number+ will be used as the name of
639
+ # the link.
640
+ #
641
+ # A +country_code+ option is supported, which prepends a plus sign and the
642
+ # given country code to the linked phone number. For example,
643
+ # <tt>country_code: "01"</tt> will prepend <tt>+01</tt> to the linked
644
+ # phone number.
645
+ #
646
+ # Additional HTML attributes for the link can be passed via +html_options+.
647
+ #
648
+ # ==== Options
649
+ # * <tt>:country_code</tt> - Prepends the country code to the phone number
650
+ #
651
+ # ==== Examples
652
+ # phone_to "1234567890"
653
+ # # => <a href="tel:1234567890">1234567890</a>
654
+ #
655
+ # phone_to "1234567890", "Phone me"
656
+ # # => <a href="tel:1234567890">Phone me</a>
657
+ #
658
+ # phone_to "1234567890", country_code: "01"
659
+ # # => <a href="tel:+011234567890">1234567890</a>
660
+ #
661
+ # You can use a block as well if your link target is hard to fit into the name parameter. \ERB example:
662
+ #
663
+ # <%= phone_to "1234567890" do %>
664
+ # <strong>Phone me:</strong>
665
+ # <% end %>
666
+ # # => <a href="tel:1234567890">
667
+ # <strong>Phone me:</strong>
668
+ # </a>
669
+ def phone_to(phone_number, name = nil, html_options = {}, &block)
670
+ html_options, name = name, nil if name.is_a?(Hash)
671
+ html_options = (html_options || {}).stringify_keys
672
+
673
+ country_code = html_options.delete("country_code").presence
674
+ country_code = country_code.nil? ? "" : "+#{ERB::Util.url_encode(country_code)}"
675
+
676
+ encoded_phone_number = ERB::Util.url_encode(phone_number)
677
+ html_options["href"] = "tel:#{country_code}#{encoded_phone_number}"
678
+
679
+ content_tag("a", name || phone_number, html_options, &block)
680
+ end
681
+
682
+ private
683
+ def convert_options_to_data_attributes(options, html_options)
684
+ if html_options
685
+ html_options = html_options.stringify_keys
686
+ html_options["data-remote"] = "true" if link_to_remote_options?(options) || link_to_remote_options?(html_options)
687
+
688
+ method = html_options.delete("method")
689
+
690
+ add_method_to_attributes!(html_options, method) if method
691
+
692
+ html_options
693
+ else
694
+ link_to_remote_options?(options) ? { "data-remote" => "true" } : {}
695
+ end
696
+ end
697
+
698
+ def url_target(name, options)
699
+ if name.respond_to?(:model_name) && options.is_a?(Hash) && options.empty?
700
+ url_for(name)
701
+ else
702
+ url_for(options)
703
+ end
704
+ end
705
+
706
+ def link_to_remote_options?(options)
707
+ if options.is_a?(Hash)
708
+ options.delete("remote") || options.delete(:remote)
709
+ end
710
+ end
711
+
712
+ def add_method_to_attributes!(html_options, method)
713
+ if method_not_get_method?(method) && !html_options["rel"]&.include?("nofollow")
714
+ if html_options["rel"].blank?
715
+ html_options["rel"] = "nofollow"
716
+ else
717
+ html_options["rel"] = "#{html_options["rel"]} nofollow"
718
+ end
719
+ end
720
+ html_options["data-method"] = method
721
+ end
722
+
723
+ def method_for_options(options)
724
+ if options.is_a?(Array)
725
+ method_for_options(options.last)
726
+ elsif options.respond_to?(:persisted?)
727
+ options.persisted? ? :patch : :post
728
+ elsif options.respond_to?(:to_model)
729
+ method_for_options(options.to_model)
730
+ end
731
+ end
732
+
733
+ STRINGIFIED_COMMON_METHODS = {
734
+ get: "get",
735
+ delete: "delete",
736
+ patch: "patch",
737
+ post: "post",
738
+ put: "put",
739
+ }.freeze
740
+
741
+ def method_not_get_method?(method)
742
+ return false unless method
743
+ (STRINGIFIED_COMMON_METHODS[method] || method.to_s.downcase) != "get"
744
+ end
745
+
746
+ def token_tag(token = nil, form_options: {})
747
+ if token != false && defined?(protect_against_forgery?) && protect_against_forgery?
748
+ token =
749
+ if token == true || token.nil?
750
+ form_authenticity_token(form_options: form_options.merge(authenticity_token: token))
751
+ else
752
+ token
753
+ end
754
+ tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token, autocomplete: "off")
755
+ else
756
+ ""
757
+ end
758
+ end
759
+
760
+ def method_tag(method)
761
+ tag("input", type: "hidden", name: "_method", value: method.to_s, autocomplete: "off")
762
+ end
763
+
764
+ # Returns an array of hashes each containing :name and :value keys
765
+ # suitable for use as the names and values of form input fields:
766
+ #
767
+ # to_form_params(name: 'David', nationality: 'Danish')
768
+ # # => [{name: 'name', value: 'David'}, {name: 'nationality', value: 'Danish'}]
769
+ #
770
+ # to_form_params(country: { name: 'Denmark' })
771
+ # # => [{name: 'country[name]', value: 'Denmark'}]
772
+ #
773
+ # to_form_params(countries: ['Denmark', 'Sweden']})
774
+ # # => [{name: 'countries[]', value: 'Denmark'}, {name: 'countries[]', value: 'Sweden'}]
775
+ #
776
+ # An optional namespace can be passed to enclose key names:
777
+ #
778
+ # to_form_params({ name: 'Denmark' }, 'country')
779
+ # # => [{name: 'country[name]', value: 'Denmark'}]
780
+ def to_form_params(attribute, namespace = nil)
781
+ attribute = if attribute.respond_to?(:permitted?)
782
+ attribute.to_h
783
+ else
784
+ attribute
785
+ end
786
+
787
+ params = []
788
+ case attribute
789
+ when Hash
790
+ attribute.each do |key, value|
791
+ prefix = namespace ? "#{namespace}[#{key}]" : key
792
+ params.push(*to_form_params(value, prefix))
793
+ end
794
+ when Array
795
+ array_prefix = "#{namespace}[]"
796
+ attribute.each do |value|
797
+ params.push(*to_form_params(value, array_prefix))
798
+ end
799
+ else
800
+ params << { name: namespace.to_s, value: attribute.to_param }
801
+ end
802
+
803
+ params.sort_by { |pair| pair[:name] }
804
+ end
805
+
806
+ def remove_trailing_slash!(url_string)
807
+ trailing_index = (url_string.index("?") || 0) - 1
808
+ url_string[trailing_index] = "" if url_string[trailing_index] == "/"
809
+ end
810
+ end
811
+ end
812
+ end