wovnrb 3.7.2 → 3.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +28 -0
- data/README.en.md +44 -15
- data/README.ja.md +51 -20
- data/README.md +1 -1
- data/docker/rails/TestSite/yarn.lock +207 -180
- data/lib/wovnrb/api_translator.rb +5 -0
- data/lib/wovnrb/custom_domain/custom_domain_lang.rb +31 -0
- data/lib/wovnrb/custom_domain/custom_domain_lang_url_handler.rb +27 -0
- data/lib/wovnrb/custom_domain/custom_domain_langs.rb +40 -0
- data/lib/wovnrb/headers.rb +62 -51
- data/lib/wovnrb/lang.rb +2 -2
- data/lib/wovnrb/services/html_converter.rb +17 -3
- data/lib/wovnrb/services/html_replace_marker.rb +4 -0
- data/lib/wovnrb/store.rb +10 -1
- data/lib/wovnrb/url_language_switcher.rb +44 -4
- data/lib/wovnrb/version.rb +1 -1
- data/lib/wovnrb.rb +7 -2
- data/test/lib/custom_domain/custom_domain_lang_test.rb +85 -0
- data/test/lib/custom_domain/custom_domain_lang_url_handler_test.rb +75 -0
- data/test/lib/custom_domain/custom_domain_langs_test.rb +82 -0
- data/test/lib/headers_test.rb +209 -48
- data/test/lib/services/html_converter_test.rb +70 -0
- data/test/lib/url_language_switcher_test.rb +148 -0
- data/test/lib/wovnrb_test.rb +1 -0
- data/wovnrb.gemspec +0 -27
- metadata +9 -311
@@ -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
|
data/lib/wovnrb/headers.rb
CHANGED
@@ -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']
|
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']
|
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
|
-
|
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
|
80
|
-
if @
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
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
|
-
@
|
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
|
120
|
+
def request_out
|
113
121
|
@env['wovnrb.target_lang'] = lang_code
|
114
122
|
case @settings['url_pattern']
|
115
123
|
when 'query'
|
116
|
-
|
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
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
-
|
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
|
-
|
142
|
-
|
143
|
-
|
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-
|
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 =
|
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
|
-
]
|
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
|
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
|
32
|
-
# @param lang
|
33
|
-
# @
|
34
|
-
|
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
|
data/lib/wovnrb/version.rb
CHANGED
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
|
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
|
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
|