wovnrb 3.7.2 → 3.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -118,6 +118,7 @@ module Wovnrb
118
118
  }
119
119
 
120
120
  result['custom_lang_aliases'] = JSON.dump(custom_lang_aliases) unless custom_lang_aliases.empty?
121
+ result['custom_domain_langs'] = JSON.dump(custom_domain_langs) unless custom_domain_langs.empty?
121
122
 
122
123
  result
123
124
  end
@@ -162,6 +163,10 @@ module Wovnrb
162
163
  @store.settings['translate_canonical_tag']
163
164
  end
164
165
 
166
+ def custom_domain_langs
167
+ @store.custom_domain_langs.to_html_swapper_hash
168
+ end
169
+
165
170
  def page_url
166
171
  "#{@headers.protocol}://#{@headers.url}"
167
172
  end
@@ -0,0 +1,31 @@
1
+ module Wovnrb
2
+ # Represents a custom domain for a given language
3
+ class CustomDomainLang
4
+ attr_accessor :host, :path, :lang
5
+
6
+ def initialize(host, path, lang)
7
+ @host = host
8
+ @path = path.end_with?('/') ? path : "#{path}/"
9
+ @lang = lang
10
+ end
11
+
12
+ # @param uri [Addressable::URI]
13
+ def match?(parsed_uri)
14
+ @host.casecmp?(parsed_uri.host) && path_is_equal_or_subset_of?(@path, parsed_uri.path)
15
+ end
16
+
17
+ def host_and_path_without_trailing_slash
18
+ host_and_path = @host + @path
19
+ host_and_path.end_with?('/') ? host_and_path.delete_suffix('/') : host_and_path
20
+ end
21
+
22
+ private
23
+
24
+ def path_is_equal_or_subset_of?(path1, path2)
25
+ path1_segments = path1.split('/').reject(&:empty?)
26
+ path2_segments = path2.split('/').reject(&:empty?)
27
+
28
+ path1_segments == path2_segments.slice(0, path1_segments.length)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ require 'wovnrb/custom_domain/custom_domain_lang'
2
+
3
+ module Wovnrb
4
+ # Helper class for transforming actual domains to user-defined custom domains
5
+ class CustomDomainLangUrlHandler
6
+ class << self
7
+ def add_custom_domain_lang_to_absolute_url(absolute_url, target_lang, custom_domain_langs)
8
+ current_custom_domain = custom_domain_langs.custom_domain_lang_by_url(absolute_url)
9
+ new_lang_custom_domain = custom_domain_langs.custom_domain_lang_by_lang(target_lang)
10
+ change_to_new_custom_domain_lang(absolute_url, current_custom_domain, new_lang_custom_domain)
11
+ end
12
+
13
+ def change_to_new_custom_domain_lang(absolute_url, current_custom_domain, new_lang_custom_domain)
14
+ return absolute_url unless current_custom_domain.present? && new_lang_custom_domain.present?
15
+
16
+ current_host_and_path = current_custom_domain.host_and_path_without_trailing_slash
17
+ new_host_and_path = new_lang_custom_domain.host_and_path_without_trailing_slash
18
+
19
+ # ^(.*://|//)? 1: schema, e.g. https://
20
+ # (#{current_host_and_path}) 2: host and path, e.g. wovn.io/foo
21
+ # ((?:/|\?|#).*)?$ 3: other / query params, e.g. ?hello=world
22
+ regex = %r{^(.*://|//)?(#{current_host_and_path})((?:/|\?|#).*)?$}
23
+ absolute_url.gsub(regex) { "#{Regexp.last_match(1)}#{new_host_and_path}#{Regexp.last_match(3)}" }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,40 @@
1
+ require 'wovnrb/custom_domain/custom_domain_lang'
2
+
3
+ module Wovnrb
4
+ # Represents a list of custom domains with corresponding languages
5
+ class CustomDomainLangs
6
+ def initialize(setting)
7
+ @custom_domain_langs = setting.map do |lang_code, config|
8
+ parsed_uri = Addressable::URI.parse(add_protocol_if_needed(config['url']))
9
+ CustomDomainLang.new(parsed_uri.host, parsed_uri.path, lang_code)
10
+ end
11
+ end
12
+
13
+ def custom_domain_lang_by_lang(lang_code)
14
+ @custom_domain_langs.find { |c| c.lang == lang_code }
15
+ end
16
+
17
+ def custom_domain_lang_by_url(uri)
18
+ parsed_uri = Addressable::URI.parse(add_protocol_if_needed(uri))
19
+
20
+ # "/" path will naturally match every URL, so by comparing longest paths first we will get the best match
21
+ @custom_domain_langs
22
+ .sort_by { |c| -c.path.length }
23
+ .find { |c| c.match?(parsed_uri) }
24
+ end
25
+
26
+ def to_html_swapper_hash
27
+ result = {}
28
+ @custom_domain_langs.each do |custom_domain_lang|
29
+ result[custom_domain_lang.host_and_path_without_trailing_slash] = custom_domain_lang.lang
30
+ end
31
+ result
32
+ end
33
+
34
+ private
35
+
36
+ def add_protocol_if_needed(url)
37
+ url.match?(%r{https?://}) ? url : "http://#{url}"
38
+ end
39
+ end
40
+ end
@@ -1,3 +1,5 @@
1
+ require 'wovnrb/custom_domain/custom_domain_lang_url_handler'
2
+
1
3
  module Wovnrb
2
4
  class Headers
3
5
  attr_reader :unmasked_url, :url, :protocol, :unmasked_host, :host, :unmasked_pathname, :pathname, :pathname_with_trailing_slash_if_present
@@ -31,9 +33,9 @@ module Wovnrb
31
33
  else
32
34
  @env['HTTP_HOST']
33
35
  end
34
- @host = settings['url_pattern'] == 'subdomain' ? @url_lang_switcher.remove_lang_from_uri_component(@host, lang_code) : @host
36
+ @host = %w[subdomain custom_domain].include?(settings['url_pattern']) ? @url_lang_switcher.remove_lang_from_uri_component(@host, lang_code, self) : @host
35
37
  @pathname, @query = @env['REQUEST_URI'].split('?')
36
- @pathname = settings['url_pattern'] == 'path' ? @url_lang_switcher.remove_lang_from_uri_component(@pathname, lang_code) : @pathname
38
+ @pathname = %w[path custom_domain].include?(settings['url_pattern']) ? @url_lang_switcher.remove_lang_from_uri_component(@pathname, lang_code, self) : @pathname
37
39
  @query ||= ''
38
40
  @url = "#{@host}#{@pathname}#{(@query.empty? ? '' : '?') + @url_lang_switcher.remove_lang_from_uri_component(@query, lang_code)}"
39
41
  if settings['query'].empty?
@@ -68,7 +70,7 @@ module Wovnrb
68
70
  #
69
71
  # @return [String] The lang code of the current page
70
72
  def lang_code
71
- path_lang && !path_lang.empty? ? path_lang : @settings['default_lang']
73
+ url_language && !url_language.empty? ? url_language : @settings['default_lang']
72
74
  end
73
75
 
74
76
  # picks up language code from requested URL by using url_pattern_reg setting.
@@ -76,21 +78,27 @@ module Wovnrb
76
78
  # if you want examples, please see test/lib/headers_test.rb.
77
79
  #
78
80
  # @return [String] language code in requrested URL.
79
- def path_lang
80
- if @path_lang.nil?
81
- rp = Regexp.new(@settings['url_pattern_reg'])
82
- match = if @settings['use_proxy'] && @env.key?('HTTP_X_FORWARDED_HOST')
83
- "#{@env['HTTP_X_FORWARDED_HOST']}#{@env['REQUEST_URI']}".match(rp)
84
- else
85
- "#{@env['SERVER_NAME']}#{@env['REQUEST_URI']}".match(rp)
86
- end
87
- @path_lang = if match && match[:lang] && Lang.get_lang(match[:lang])
88
- Lang.get_code(match[:lang])
89
- else
90
- ''
91
- end
81
+ def url_language
82
+ if @url_language.nil?
83
+ full_url = if @settings['use_proxy'] && @env.key?('HTTP_X_FORWARDED_HOST')
84
+ "#{@env['HTTP_X_FORWARDED_HOST']}#{@env['REQUEST_URI']}"
85
+ else
86
+ "#{@env['SERVER_NAME']}#{@env['REQUEST_URI']}"
87
+ end
88
+
89
+ new_lang_code = nil
90
+ if @settings['url_pattern'] == 'custom_domain'
91
+ custom_domain_langs = Store.instance.custom_domain_langs
92
+ custom_domain = custom_domain_langs.custom_domain_lang_by_url(full_url)
93
+ new_lang_code = custom_domain.lang if custom_domain.present?
94
+ else
95
+ rp = Regexp.new(@settings['url_pattern_reg'])
96
+ match = full_url.match(rp)
97
+ new_lang_code = Lang.get_code(match[:lang]) if match && match[:lang] && Lang.get_lang(match[:lang])
98
+ end
99
+ @url_language = new_lang_code.presence || ''
92
100
  end
93
- @path_lang
101
+ @url_language
94
102
  end
95
103
 
96
104
  def redirect(lang)
@@ -109,51 +117,28 @@ module Wovnrb
109
117
  @url_lang_switcher.add_lang_code(url_with_scheme, lang, self)
110
118
  end
111
119
 
112
- def request_out(_def_lang = @settings['default_lang'])
120
+ def request_out
113
121
  @env['wovnrb.target_lang'] = lang_code
114
122
  case @settings['url_pattern']
115
123
  when 'query'
116
- @env['REQUEST_URI'] = @url_lang_switcher.remove_lang_from_uri_component(@env['REQUEST_URI'], lang_code) if @env.key?('REQUEST_URI')
117
- @env['QUERY_STRING'] = @url_lang_switcher.remove_lang_from_uri_component(@env['QUERY_STRING'], lang_code) if @env.key?('QUERY_STRING')
118
- @env['ORIGINAL_FULLPATH'] = @url_lang_switcher.remove_lang_from_uri_component(@env['ORIGINAL_FULLPATH'], lang_code) if @env.key?('ORIGINAL_FULLPATH')
124
+ remove_lang_from_query
119
125
  when 'subdomain'
120
- if @settings['use_proxy'] && @env.key?('HTTP_X_FORWARDED_HOST')
121
- @env['HTTP_X_FORWARDED_HOST'] = @url_lang_switcher.remove_lang_from_uri_component(@env['HTTP_X_FORWARDED_HOST'], lang_code)
122
- else
123
- @env['HTTP_HOST'] = @url_lang_switcher.remove_lang_from_uri_component(@env['HTTP_HOST'], lang_code)
124
- @env['SERVER_NAME'] = @url_lang_switcher.remove_lang_from_uri_component(@env['SERVER_NAME'], lang_code)
125
- end
126
- @env['HTTP_REFERER'] = @url_lang_switcher.remove_lang_from_uri_component(@env['HTTP_REFERER'], lang_code) if @env.key?('HTTP_REFERER')
127
- # when 'path'
126
+ remove_lang_from_host
127
+ when 'custom_domain'
128
+ remove_lang_from_host
129
+ remove_lang_from_path
130
+ # when 'path'
128
131
  else
129
- @env['REQUEST_URI'] = @url_lang_switcher.remove_lang_from_uri_component(@env['REQUEST_URI'], lang_code)
130
- @env['REQUEST_PATH'] = @url_lang_switcher.remove_lang_from_uri_component(@env['REQUEST_PATH'], lang_code) if @env.key?('REQUEST_PATH')
131
- @env['PATH_INFO'] = @url_lang_switcher.remove_lang_from_uri_component(@env['PATH_INFO'], lang_code)
132
- @env['ORIGINAL_FULLPATH'] = @url_lang_switcher.remove_lang_from_uri_component(@env['ORIGINAL_FULLPATH'], lang_code) if @env.key?('ORIGINAL_FULLPATH')
133
- @env['HTTP_REFERER'] = @url_lang_switcher.remove_lang_from_uri_component(@env['HTTP_REFERER'], lang_code) if @env.key?('HTTP_REFERER')
132
+ remove_lang_from_path
134
133
  end
135
134
  @env
136
135
  end
137
136
 
138
137
  def out(headers)
139
- r = Regexp.new("//#{@host}")
140
138
  lang_code = Store.instance.settings['custom_lang_aliases'][self.lang_code] || self.lang_code
141
- if lang_code != @settings['default_lang'] && headers.key?('Location') && headers['Location'] =~ r && !@settings['ignore_globs'].ignore?(headers['Location'])
142
- case @settings['url_pattern']
143
- when 'query'
144
- headers['Location'] += if /\?/.match?(headers['Location'])
145
- '&'
146
- else
147
- '?'
148
- end
149
- headers['Location'] += "#{@settings['lang_param_name']}=#{lang_code}"
150
- when 'subdomain'
151
- headers['Location'] = headers['Location'].sub(/\/\/([^.]+)/, "//#{lang_code}.\\1")
152
- # when 'path'
153
- else
154
- headers['Location'] = headers['Location'].sub(/(\/\/[^\/]+)/, "\\1/#{lang_code}")
155
- end
156
- end
139
+ should_add_lang_code = lang_code != @settings['default_lang'] && headers.key?('Location') && !@settings['ignore_globs'].ignore?(headers['Location'])
140
+
141
+ headers['Location'] = @url_lang_switcher.add_lang_code(headers['Location'], lang_code, self) if should_add_lang_code
157
142
  headers
158
143
  end
159
144
 
@@ -177,5 +162,31 @@ module Wovnrb
177
162
  absolute_path = absolute_path.starts_with?('/') ? absolute_path : URL.join_paths(dirname, absolute_path)
178
163
  URL.normalize_path_slash(path, absolute_path)
179
164
  end
165
+
166
+ private
167
+
168
+ def remove_lang_from_query
169
+ @env['REQUEST_URI'] = @url_lang_switcher.remove_lang_from_uri_component(@env['REQUEST_URI'], lang_code) if @env.key?('REQUEST_URI')
170
+ @env['QUERY_STRING'] = @url_lang_switcher.remove_lang_from_uri_component(@env['QUERY_STRING'], lang_code) if @env.key?('QUERY_STRING')
171
+ @env['ORIGINAL_FULLPATH'] = @url_lang_switcher.remove_lang_from_uri_component(@env['ORIGINAL_FULLPATH'], lang_code) if @env.key?('ORIGINAL_FULLPATH')
172
+ end
173
+
174
+ def remove_lang_from_host
175
+ if @settings['use_proxy'] && @env.key?('HTTP_X_FORWARDED_HOST')
176
+ @env['HTTP_X_FORWARDED_HOST'] = @url_lang_switcher.remove_lang_from_uri_component(@env['HTTP_X_FORWARDED_HOST'], lang_code, self)
177
+ else
178
+ @env['HTTP_HOST'] = @url_lang_switcher.remove_lang_from_uri_component(@env['HTTP_HOST'], lang_code, self)
179
+ @env['SERVER_NAME'] = @url_lang_switcher.remove_lang_from_uri_component(@env['SERVER_NAME'], lang_code, self)
180
+ end
181
+ @env['HTTP_REFERER'] = @url_lang_switcher.remove_lang_from_uri_component(@env['HTTP_REFERER'], lang_code, self) if @env.key?('HTTP_REFERER')
182
+ end
183
+
184
+ def remove_lang_from_path
185
+ @env['REQUEST_URI'] = @url_lang_switcher.remove_lang_from_uri_component(@env['REQUEST_URI'], lang_code, self)
186
+ @env['REQUEST_PATH'] = @url_lang_switcher.remove_lang_from_uri_component(@env['REQUEST_PATH'], lang_code, self) if @env.key?('REQUEST_PATH')
187
+ @env['PATH_INFO'] = @url_lang_switcher.remove_lang_from_uri_component(@env['PATH_INFO'], lang_code, self)
188
+ @env['ORIGINAL_FULLPATH'] = @url_lang_switcher.remove_lang_from_uri_component(@env['ORIGINAL_FULLPATH'], lang_code, self) if @env.key?('ORIGINAL_FULLPATH')
189
+ @env['HTTP_REFERER'] = @url_lang_switcher.remove_lang_from_uri_component(@env['HTTP_REFERER'], lang_code, self) if @env.key?('HTTP_REFERER')
190
+ end
180
191
  end
181
192
  end
data/lib/wovnrb/lang.rb CHANGED
@@ -56,7 +56,7 @@ module Wovnrb
56
56
  'pt-PT' => { name: 'Português (Portugal)', code: 'pt-PT', en: 'Portuguese (Portugal)' },
57
57
  'ru' => { name: 'Русский', code: 'ru', en: 'Russian' },
58
58
  'es' => { name: 'Español', code: 'es', en: 'Spanish' },
59
- 'es-RA' => { name: 'Español (Argentina)', code: 'es-RA', en: 'Spanish (Argentina)' },
59
+ 'es-AR' => { name: 'Español (Argentina)', code: 'es-AR', en: 'Spanish (Argentina)' },
60
60
  'es-CL' => { name: 'Español (Chile)', code: 'es-CL', en: 'Spanish (Chile)' },
61
61
  'es-CO' => { name: 'Español (Colombia)', code: 'es-CO', en: 'Spanish (Colombia)' },
62
62
  'es-CR' => { name: 'Español (Costa Rica)', code: 'es-CR', en: 'Spanish (Costa Rica)' },
@@ -250,7 +250,7 @@ module Wovnrb
250
250
  end
251
251
 
252
252
  def add_query_lang_code(href, lang_code, lang_param_name)
253
- query_separator = /\?/.match?(href) ? '&' : '?'
253
+ query_separator = href.include?('?') ? '&' : '?'
254
254
 
255
255
  href.sub(/(#|$)/, "#{query_separator}#{lang_param_name}=#{lang_code}\\1")
256
256
  end
@@ -15,12 +15,23 @@ module Wovnrb
15
15
  def build_api_compatible_html
16
16
  marker = HtmlReplaceMarker.new
17
17
  converted_html = replace_dom(marker)
18
-
18
+ converted_html = remove_backend_wovn_ignore_comments(converted_html, marker)
19
19
  [converted_html, marker]
20
20
  end
21
21
 
22
22
  private
23
23
 
24
+ def remove_backend_wovn_ignore_comments(html, marker)
25
+ backend_ignore_regex = /(<!--\s*backend-wovn-ignore\s*-->)(.+?)(<!--\s*\/backend-wovn-ignore\s*-->)/m
26
+ html.gsub(backend_ignore_regex) do |_match|
27
+ comment_start = Regexp.last_match(1)
28
+ ignored_content = Regexp.last_match(2)
29
+ comment_end = Regexp.last_match(3)
30
+ key = marker.add_comment_value(ignored_content)
31
+ comment_start + key + comment_end
32
+ end
33
+ end
34
+
24
35
  def html
25
36
  # Ensure a Content-Type declaration in the header. This mimics Nokogumbo
26
37
  # 1.5.0 default serialization behavior.
@@ -196,8 +207,9 @@ module Wovnrb
196
207
  url_pattern = @store.settings['url_pattern']
197
208
  lang_code_aliases_json = JSON.generate(@store.settings['custom_lang_aliases'])
198
209
  lang_param_name = @store.settings['lang_param_name']
210
+ custom_domain_langs = @store.custom_domain_langs.to_html_swapper_hash
199
211
 
200
- [
212
+ result = [
201
213
  "key=#{token}",
202
214
  'backend=true',
203
215
  "currentLang=#{current_lang}",
@@ -206,7 +218,9 @@ module Wovnrb
206
218
  "langCodeAliases=#{lang_code_aliases_json}",
207
219
  "langParamName=#{lang_param_name}",
208
220
  "version=WOVN.rb_#{VERSION}"
209
- ].join('&')
221
+ ]
222
+ result << "customDomainLangs=#{JSON.generate(custom_domain_langs)}" unless custom_domain_langs.empty?
223
+ result.join('&')
210
224
  end
211
225
  end
212
226
  end
@@ -32,6 +32,10 @@ module Wovnrb
32
32
  marked_html
33
33
  end
34
34
 
35
+ def keys
36
+ @mapped_values.map { |v| v[0] }
37
+ end
38
+
35
39
  private
36
40
 
37
41
  def generate_key
data/lib/wovnrb/store.rb CHANGED
@@ -2,6 +2,7 @@ require 'net/http'
2
2
  require 'uri'
3
3
  require 'cgi'
4
4
  require 'singleton'
5
+ require 'wovnrb/custom_domain/custom_domain_langs'
5
6
  require 'wovnrb/services/wovn_logger'
6
7
  require 'wovnrb/services/glob'
7
8
  require 'wovnrb/settings'
@@ -38,7 +39,8 @@ module Wovnrb
38
39
  'widget_url' => 'https://j.wovn.io/1',
39
40
  'wovn_dev_mode' => false,
40
41
  'compress_api_requests' => true,
41
- 'translate_canonical_tag' => true
42
+ 'translate_canonical_tag' => true,
43
+ 'custom_domain_langs' => {}
42
44
  )
43
45
  end
44
46
 
@@ -51,6 +53,7 @@ module Wovnrb
51
53
  # @return [nil]
52
54
  def reset
53
55
  @settings = Store.default_settings
56
+ @custom_domain_langs = nil
54
57
  # When Store is initialized, the Rails.configuration object is not yet initialized
55
58
  @config_loaded = false
56
59
  end
@@ -152,6 +155,8 @@ module Wovnrb
152
155
  @settings['url_pattern_reg'] = "((\\?.*&)|\\?)#{@settings['lang_param_name']}=(?<lang>[^&]+)(&|$)"
153
156
  when 'subdomain'
154
157
  @settings['url_pattern_reg'] = '^(?<lang>[^.]+)\.'
158
+ when 'custom_domain'
159
+ # Do not use regex
155
160
  end
156
161
 
157
162
  @settings['test_mode'] = !(@settings['test_mode'] != true || @settings['test_mode'] != 'on')
@@ -200,6 +205,10 @@ module Wovnrb
200
205
  @settings['url_pattern']
201
206
  end
202
207
 
208
+ def custom_domain_langs
209
+ @custom_domain_langs ||= CustomDomainLangs.new(@settings['custom_domain_langs'])
210
+ end
211
+
203
212
  private
204
213
 
205
214
  def stringify_keys!(hash)
@@ -1,3 +1,4 @@
1
+ require 'wovnrb/custom_domain/custom_domain_lang_url_handler'
1
2
  require 'wovnrb/services/url'
2
3
 
3
4
  module Wovnrb
@@ -28,10 +29,11 @@ module Wovnrb
28
29
 
29
30
  # Removes language code from a URI component (path, hostname, query etc), and returns it.
30
31
  #
31
- # @param uri [String] original URI component
32
- # @param lang [String] language code
33
- # @return [String] removed URI
34
- def remove_lang_from_uri_component(uri, lang)
32
+ # @param uri [String] original URI component
33
+ # @param lang [String] language code
34
+ # @param headers [Header] headers
35
+ # @return [String] removed URI
36
+ def remove_lang_from_uri_component(uri, lang, headers = nil)
35
37
  lang_code = @store.settings['custom_lang_aliases'][lang] || lang
36
38
 
37
39
  return uri if lang_code.blank?
@@ -49,6 +51,25 @@ module Wovnrb
49
51
  # (/|$) 3: path or end-of-string
50
52
  lang_code_pattern = %r{^(.*://|//)?([^/]*/)?#{lang_code}(/|$)}
51
53
  uri.sub(lang_code_pattern, '\1\2')
54
+ when 'custom_domain'
55
+ custom_domain_langs = @store.custom_domain_langs
56
+ custom_domain_lang_to_remove = custom_domain_langs.custom_domain_lang_by_lang(lang_code)
57
+ default_custom_domain_lang = custom_domain_langs.custom_domain_lang_by_lang(@store.default_lang)
58
+ new_uri = uri
59
+ if Wovnrb::URL.absolute_url?(uri)
60
+ new_uri = CustomDomainLangUrlHandler.change_to_new_custom_domain_lang(uri, custom_domain_lang_to_remove, default_custom_domain_lang)
61
+ elsif Wovnrb::URL.absolute_path?(uri)
62
+ absolute_url = "#{headers.protocol}://#{headers.unmasked_host}#{uri}"
63
+ absolute_url_with_lang = CustomDomainLangUrlHandler.change_to_new_custom_domain_lang(absolute_url, custom_domain_lang_to_remove, default_custom_domain_lang)
64
+ segments = make_segments_from_absolute_url(absolute_url_with_lang)
65
+ new_uri = segments['others']
66
+ elsif uri == custom_domain_lang_to_remove.host
67
+ absolute_url = "#{headers.protocol}://#{uri}#{headers.unmasked_pathname}"
68
+ absolute_url_with_lang = CustomDomainLangUrlHandler.change_to_new_custom_domain_lang(absolute_url, custom_domain_lang_to_remove, default_custom_domain_lang)
69
+ segments = make_segments_from_absolute_url(absolute_url_with_lang)
70
+ new_uri = segments['host']
71
+ end
72
+ new_uri
52
73
  else
53
74
  raise RuntimeError("Invalid URL pattern: #{@store.settings['url_pattern']}")
54
75
  end
@@ -80,6 +101,8 @@ module Wovnrb
80
101
  end
81
102
  when 'query'
82
103
  add_query_lang_code(href, code_to_add)
104
+ when 'custom_domain'
105
+ CustomDomainLangUrlHandler.add_custom_domain_lang_to_absolute_url(href, code_to_add, @store.custom_domain_langs)
83
106
  else # path
84
107
  href_uri.path = add_lang_code_for_path(href_uri.path, code_to_add, headers)
85
108
  href_uri.to_s
@@ -105,6 +128,9 @@ module Wovnrb
105
128
  "#{headers.protocol}://#{code_to_add.downcase}.#{headers.host}#{abs_path}"
106
129
  when 'query'
107
130
  add_query_lang_code(href, code_to_add)
131
+ when 'custom_domain'
132
+ absolute_url = "#{headers.protocol}://#{headers.host}#{abs_path}"
133
+ CustomDomainLangUrlHandler.add_custom_domain_lang_to_absolute_url(absolute_url, code_to_add, @store.custom_domain_langs)
108
134
  else # path
109
135
  add_lang_code_for_path(href, code_to_add, headers)
110
136
  end
@@ -148,5 +174,19 @@ module Wovnrb
148
174
  def build_lang_path(lang_code)
149
175
  lang_code.blank? ? '' : URL.prepend_path_slash(lang_code)
150
176
  end
177
+
178
+ def make_segments_from_absolute_url(absolute_uri)
179
+ # 1: schema (optional) like https://
180
+ # 2: host (optional) like wovn.io
181
+ # 3: path with query or hash
182
+ regex = %r{^(.*://|//)?([^/?]*)?((?:/|\?|#).*)?$}
183
+ matches = regex.match(absolute_uri)
184
+
185
+ {
186
+ 'schema' => matches[1],
187
+ 'host' => matches[2],
188
+ 'others' => matches[3]
189
+ }.transform_values(&:to_s)
190
+ end
151
191
  end
152
192
  end
@@ -1,3 +1,3 @@
1
1
  module Wovnrb
2
- VERSION = '3.7.2'.freeze
2
+ VERSION = '3.9.0'.freeze
3
3
  end
data/lib/wovnrb.rb CHANGED
@@ -36,7 +36,7 @@ module Wovnrb
36
36
  return @app.call(env) if @store.settings['test_mode'] && @store.settings['test_url'] != headers.url
37
37
 
38
38
  # redirect if the path is set to the default language (for SEO purposes)
39
- if headers.path_lang == default_lang
39
+ if explicit_default_lang?(headers)
40
40
  redirect_headers = headers.redirect(default_lang)
41
41
  return [307, redirect_headers, ['']]
42
42
  end
@@ -51,7 +51,7 @@ module Wovnrb
51
51
  status, res_headers, body = @app.call(headers.request_out)
52
52
 
53
53
  # disabled by next Rack middleware
54
- return output(headers, status, res_headers, body) unless /html/.match?(res_headers['Content-Type'])
54
+ return output(headers, status, res_headers, body) unless res_headers['Content-Type']&.include?('html')
55
55
 
56
56
  request = Rack::Request.new(env)
57
57
 
@@ -129,5 +129,10 @@ module Wovnrb
129
129
 
130
130
  !!(html_attributes['amp'] || html_attributes["\u26A1"])
131
131
  end
132
+
133
+ def explicit_default_lang?(headers)
134
+ default_lang, url_pattern = @store.settings.values_at('default_lang', 'url_pattern')
135
+ default_lang == headers.url_language && url_pattern != 'custom_domain'
136
+ end
132
137
  end
133
138
  end
@@ -0,0 +1,85 @@
1
+ require 'test_helper'
2
+ require 'wovnrb/custom_domain/custom_domain_lang'
3
+
4
+ module Wovnrb
5
+ class CustomDomainLangTest < WovnMiniTest
6
+ def setup
7
+ @custom_domain_root_path = CustomDomainLang.new('foo.com', '/', 'fr')
8
+ @custom_domain_with_path_no_trailing_slash = CustomDomainLang.new('foo.com', '/path', 'fr')
9
+ @custom_domain_with_path_trailing_slash = CustomDomainLang.new('foo.com', '/path/', 'fr')
10
+ @custom_domain_path_encoded_spaces = CustomDomainLang.new('foo.com', '/dir%20path', 'fr')
11
+ end
12
+
13
+ def test_custom_domain_lang_params
14
+ assert_equal('foo.com', @custom_domain_root_path.host)
15
+ assert_equal('/', @custom_domain_root_path.path)
16
+ assert_equal('fr', @custom_domain_root_path.lang)
17
+ assert_equal('foo.com', @custom_domain_root_path.host_and_path_without_trailing_slash)
18
+
19
+ assert_equal('foo.com', @custom_domain_with_path_no_trailing_slash.host)
20
+ assert_equal('/path/', @custom_domain_with_path_no_trailing_slash.path)
21
+ assert_equal('fr', @custom_domain_with_path_no_trailing_slash.lang)
22
+ assert_equal('foo.com/path', @custom_domain_with_path_no_trailing_slash.host_and_path_without_trailing_slash)
23
+
24
+ assert_equal('foo.com', @custom_domain_with_path_trailing_slash.host)
25
+ assert_equal('/path/', @custom_domain_with_path_trailing_slash.path)
26
+ assert_equal('fr', @custom_domain_with_path_trailing_slash.lang)
27
+ assert_equal('foo.com/path', @custom_domain_with_path_trailing_slash.host_and_path_without_trailing_slash)
28
+
29
+ assert_equal('foo.com', @custom_domain_path_encoded_spaces.host)
30
+ assert_equal('/dir%20path/', @custom_domain_path_encoded_spaces.path)
31
+ assert_equal('fr', @custom_domain_path_encoded_spaces.lang)
32
+ assert_equal('foo.com/dir%20path', @custom_domain_path_encoded_spaces.host_and_path_without_trailing_slash)
33
+ end
34
+
35
+ def test_is_match_with_different_domain
36
+ refute(@custom_domain_root_path.match?(Addressable::URI.parse('http://otherdomain.com/other/test.html')))
37
+ end
38
+
39
+ def test_is_match_with_different_port_number_should_be_ignored
40
+ assert(@custom_domain_root_path.match?(Addressable::URI.parse('http://foo.com:3000/other/test.html')))
41
+ assert(@custom_domain_root_path.match?(Addressable::URI.parse('http://foo.com:80/other/test.html')))
42
+ assert(@custom_domain_root_path.match?(Addressable::URI.parse('http://foo.com/other/test.html')))
43
+ end
44
+
45
+ def test_is_match_with_domain_containing_substring_should_be_false
46
+ refute(@custom_domain_root_path.match?(Addressable::URI.parse('http://en.foo.com/other/test.html')))
47
+ end
48
+
49
+ def test_is_match_with_same_domain_should_be_true
50
+ assert(@custom_domain_root_path.match?(Addressable::URI.parse('http://foo.com/other/test.html')))
51
+ end
52
+
53
+ def test_is_match_with_same_domain_different_casing_should_be_true
54
+ assert(@custom_domain_root_path.match?(Addressable::URI.parse('http://foo.com/other/test.html')))
55
+ end
56
+
57
+ def test_is_match_with_path_starts_with_custom_path_should_be_true
58
+ assert(@custom_domain_root_path.match?(Addressable::URI.parse('http://foo.com')))
59
+ assert(@custom_domain_root_path.match?(Addressable::URI.parse('http://foo.com/')))
60
+ assert(@custom_domain_root_path.match?(Addressable::URI.parse('http://foo.com/other/test.html?foo=bar')))
61
+
62
+ assert(@custom_domain_with_path_no_trailing_slash.match?(Addressable::URI.parse('http://foo.com/path')))
63
+ assert(@custom_domain_with_path_no_trailing_slash.match?(Addressable::URI.parse('http://foo.com/path/')))
64
+ assert(@custom_domain_with_path_no_trailing_slash.match?(Addressable::URI.parse('http://foo.com/path/other/test.html?foo=bar')))
65
+
66
+ assert(@custom_domain_with_path_trailing_slash.match?(Addressable::URI.parse('http://foo.com/path')))
67
+ assert(@custom_domain_with_path_trailing_slash.match?(Addressable::URI.parse('http://foo.com/path/')))
68
+ assert(@custom_domain_with_path_trailing_slash.match?(Addressable::URI.parse('http://foo.com/path/other/test.html?foo=bar')))
69
+
70
+ assert(@custom_domain_path_encoded_spaces.match?(Addressable::URI.parse('http://foo.com/dir%20path')))
71
+ assert(@custom_domain_path_encoded_spaces.match?(Addressable::URI.parse('http://foo.com/dir%20path?foo=bar')))
72
+ end
73
+
74
+ def test_is_match_with_path_matches_substring_should_be_false
75
+ refute(@custom_domain_with_path_no_trailing_slash.match?(Addressable::URI.parse('http://foo.com/pathsuffix/other/test.html')))
76
+ refute(@custom_domain_with_path_trailing_slash.match?(Addressable::URI.parse('http://foo.com/pathsuffix/other/test.html')))
77
+ refute(@custom_domain_path_encoded_spaces.match?(Addressable::URI.parse('http://foo.com/dir%20pathsuffix/other/test.html')))
78
+ end
79
+
80
+ def test_is_match_with_path_matches_custom_path_as_suffix_should_be_false
81
+ refute(@custom_domain_with_path_no_trailing_slash.match?(Addressable::URI.parse('http://foo.com/images/path/foo.png')))
82
+ refute(@custom_domain_with_path_trailing_slash.match?(Addressable::URI.parse('http://foo.com/images/path/foo.png')))
83
+ end
84
+ end
85
+ end