wovnrb 3.8.0 → 3.10.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/wovnrb/store.rb CHANGED
@@ -1,211 +1,221 @@
1
- require 'net/http'
2
- require 'uri'
3
- require 'cgi'
4
- require 'singleton'
5
- require 'wovnrb/services/wovn_logger'
6
- require 'wovnrb/services/glob'
7
- require 'wovnrb/settings'
8
- require 'active_support'
9
- require 'active_support/core_ext'
10
-
11
- module Wovnrb
12
- class Store
13
- include Singleton
14
-
15
- def self.default_settings
16
- Settings.new.merge(
17
- 'project_token' => '',
18
- 'log_path' => 'log/wovn_error.log',
19
- 'ignore_paths' => [],
20
- 'ignore_globs' => [],
21
- 'url_pattern' => 'path',
22
- 'url_pattern_reg' => "/(?<lang>[^/.?]+)",
23
- 'lang_param_name' => 'wovn',
24
- 'query' => [],
25
- 'ignore_class' => [],
26
- 'api_url' => 'https://wovn.global.ssl.fastly.net',
27
- 'api_timeout_seconds' => 1.0,
28
- 'api_timeout_search_engine_bots' => 5.0,
29
- 'default_lang' => 'ja',
30
- 'supported_langs' => %w[en ja],
31
- 'test_mode' => false,
32
- 'test_url' => '',
33
- 'cache_megabytes' => nil,
34
- 'ttl_seconds' => nil,
35
- 'use_proxy' => false, # use env['HTTP_X_FORWARDED_HOST'] instead of env['HTTP_HOST'] and env['SERVER_NAME'] when this setting is true.
36
- 'custom_lang_aliases' => {},
37
- 'translate_fragment' => true,
38
- 'widget_url' => 'https://j.wovn.io/1',
39
- 'wovn_dev_mode' => false,
40
- 'compress_api_requests' => true,
41
- 'translate_canonical_tag' => true
42
- )
43
- end
44
-
45
- def initialize
46
- reset
47
- end
48
-
49
- # Reset @settings and @config_loaded variables to default.
50
- #
51
- # @return [nil]
52
- def reset
53
- @settings = Store.default_settings
54
- # When Store is initialized, the Rails.configuration object is not yet initialized
55
- @config_loaded = false
56
- end
57
-
58
- # Returns true or false based on whether the token is valid or not
59
- #
60
- # @return [Boolean] Returns true if the token is valid, and false if it is not
61
- def valid_token?(token)
62
- !token.nil? && (token.length == 5 || token.length == 6)
63
- end
64
-
65
- # Returns true or false based on whether the settings are valid or not, logs any invalid settings to ../error.log
66
- #
67
- # @return [Boolean] Returns true if the settings are valid, and false if they are not
68
- def valid_settings?
69
- valid = true
70
- errors = []
71
- # if valid_token?(!settings.has_key?('project_token') || settings['project_token'].length < 5 || settings['project_token'].length > 6
72
- if !valid_token?(settings['project_token'])
73
- errors.push("Project token #{settings['project_token']} is not valid.")
74
- end
75
- if settings.has_key?('ignore_paths') && !settings['ignore_paths'].kind_of?(Array)
76
- errors.push("Ignore Paths #{settings['ignore_paths']} should be Array.")
77
- end
78
- if !settings.has_key?('url_pattern') || settings['url_pattern'].length == 0
79
- errors.push("Url pattern #{settings['url_pattern']} is not valid.")
80
- end
81
- if !settings.has_key?('query') || !settings['query'].kind_of?(Array)
82
- errors.push("query config #{settings['query']} is not valid.")
83
- end
84
- if !settings.has_key?('ignore_class') || !settings['ignore_class'].kind_of?(Array)
85
- errors.push("ignore_class config #{settings['ignore_class']} should be Array.")
86
- end
87
- if !settings.has_key?('api_url') || settings['api_url'].length == 0
88
- errors.push("API URL is not configured.")
89
- end
90
- if !settings.has_key?('default_lang') || settings['default_lang'].nil?
91
- errors.push("Default lang #{settings['default_lang']} is not valid.")
92
- end
93
- if !settings.has_key?('supported_langs') || !settings['supported_langs'].kind_of?(Array) || settings['supported_langs'].size < 1
94
- errors.push("Supported langs configuration is not valid.")
95
- end
96
- if !settings.has_key?('custom_lang_aliases') || !settings['custom_lang_aliases'].kind_of?(Hash)
97
- errors.push("Custom lang aliases is not valid.")
98
- end
99
- # log errors
100
- if errors.length > 0
101
- valid = false
102
- errors.each do |e|
103
- WovnLogger.instance.error(e)
104
- end
105
- end
106
- valid
107
- end
108
-
109
- # Returns the settings object, pulling from Rails config the first time this is called
110
- #
111
- def settings
112
- load_settings unless @config_loaded
113
- @settings
114
- end
115
-
116
- # Load Rails config.wovnrb
117
- #
118
- def load_settings
119
- if Object.const_defined?(:Rails) && Rails.configuration.respond_to?(:wovnrb)
120
- @config_loaded = true
121
- update_settings(Rails.configuration.wovnrb)
122
- end
123
- end
124
-
125
- def update_settings(new_settings)
126
- load_settings unless @config_loaded
127
- if !new_settings.nil?
128
- @settings.merge!(new_settings.stringify_keys)
129
- format_settings
130
- end
131
- end
132
-
133
- def format_settings
134
- if @settings.has_key?('custom_lang_aliases')
135
- stringify_keys! @settings['custom_lang_aliases']
136
- end
137
-
138
- @settings['default_lang'] = Lang.get_code(@settings['default_lang'])
139
- if !@settings.has_key?('supported_langs')
140
- @settings['supported_langs'] = [@settings['default_lang']]
141
- end
142
-
143
- if @settings.has_key?('user_token') && @settings['project_token'].empty?
144
- @settings['project_token'] = @settings['user_token']
145
- end
146
- @settings.delete('user_token')
147
-
148
- case @settings['url_pattern']
149
- when 'path'
150
- @settings['url_pattern_reg'] = "/(?<lang>[^/.?]+)"
151
- when 'query'
152
- @settings['url_pattern_reg'] = "((\\?.*&)|\\?)#{@settings['lang_param_name']}=(?<lang>[^&]+)(&|$)"
153
- when 'subdomain'
154
- @settings['url_pattern_reg'] = '^(?<lang>[^.]+)\.'
155
- end
156
-
157
- @settings['test_mode'] = !(@settings['test_mode'] != true || @settings['test_mode'] != 'on')
158
-
159
- if @settings['wovn_dev_mode']
160
- if @settings['api_url'] == self.class.default_settings['api_url']
161
- @settings['api_url'] = 'http://dev-wovn.io:3001'
162
- end
163
-
164
- if @settings['api_timeout_seconds'] == self.class.default_settings['api_timeout_seconds']
165
- @settings['api_timeout_seconds'] = 3.0
166
- end
167
- end
168
- end
169
-
170
- def custom_lang_aliases
171
- @settings['custom_lang_aliases'] || {}
172
- end
173
-
174
- def default_lang
175
- @settings['default_lang']
176
- end
177
-
178
- def default_lang_alias
179
- custom_alias = custom_lang_aliases[default_lang]
180
- custom_alias || default_lang
181
- end
182
-
183
- def supported_langs
184
- @settings['supported_langs'] || []
185
- end
186
-
187
- def compress_api_requests?
188
- @settings['compress_api_requests']
189
- end
190
-
191
- def widget_url
192
- @settings['widget_url'] || 'https://j.wovn.io/1'
193
- end
194
-
195
- def dev_mode?
196
- @settings['wovn_dev_mode']
197
- end
198
-
199
- def url_pattern
200
- @settings['url_pattern']
201
- end
202
-
203
- private
204
-
205
- def stringify_keys!(hash)
206
- hash.keys.each do |k|
207
- hash[k.to_s] = hash.delete(k)
208
- end
209
- end
210
- end
211
- end
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'cgi'
4
+ require 'singleton'
5
+ require 'wovnrb/custom_domain/custom_domain_langs'
6
+ require 'wovnrb/services/wovn_logger'
7
+ require 'wovnrb/services/glob'
8
+ require 'wovnrb/settings'
9
+ require 'active_support'
10
+ require 'active_support/core_ext'
11
+
12
+ module Wovnrb
13
+ class Store
14
+ include Singleton
15
+
16
+ def self.default_settings
17
+ Settings.new.merge(
18
+ 'project_token' => '',
19
+ 'log_path' => 'log/wovn_error.log',
20
+ 'ignore_paths' => [],
21
+ 'ignore_globs' => [],
22
+ 'url_pattern' => 'path',
23
+ 'url_pattern_reg' => "/(?<lang>[^/.?]+)",
24
+ 'lang_param_name' => 'wovn',
25
+ 'query' => [],
26
+ 'ignore_class' => [],
27
+ 'api_url' => 'https://wovn.global.ssl.fastly.net',
28
+ 'api_timeout_seconds' => 1.0,
29
+ 'api_timeout_search_engine_bots' => 5.0,
30
+ 'default_lang' => 'ja',
31
+ 'supported_langs' => %w[en ja],
32
+ 'test_mode' => false,
33
+ 'test_url' => '',
34
+ 'cache_megabytes' => nil,
35
+ 'ttl_seconds' => nil,
36
+ 'use_proxy' => false, # use env['HTTP_X_FORWARDED_HOST'] instead of env['HTTP_HOST'] and env['SERVER_NAME'] when this setting is true.
37
+ 'custom_lang_aliases' => {},
38
+ 'translate_fragment' => true,
39
+ 'widget_url' => 'https://j.wovn.io/1',
40
+ 'wovn_dev_mode' => false,
41
+ 'compress_api_requests' => true,
42
+ 'translate_canonical_tag' => true,
43
+ 'custom_domain_langs' => {},
44
+ 'insert_hreflangs' => true
45
+ )
46
+ end
47
+
48
+ def initialize
49
+ reset
50
+ end
51
+
52
+ # Reset @settings and @config_loaded variables to default.
53
+ #
54
+ # @return [nil]
55
+ def reset
56
+ @settings = Store.default_settings
57
+ @custom_domain_langs = nil
58
+ # When Store is initialized, the Rails.configuration object is not yet initialized
59
+ @config_loaded = false
60
+ end
61
+
62
+ # Returns true or false based on whether the token is valid or not
63
+ #
64
+ # @return [Boolean] Returns true if the token is valid, and false if it is not
65
+ def valid_token?(token)
66
+ !token.nil? && (token.length == 5 || token.length == 6)
67
+ end
68
+
69
+ # Returns true or false based on whether the settings are valid or not, logs any invalid settings to ../error.log
70
+ #
71
+ # @return [Boolean] Returns true if the settings are valid, and false if they are not
72
+ def valid_settings?
73
+ valid = true
74
+ errors = []
75
+ # if valid_token?(!settings.has_key?('project_token') || settings['project_token'].length < 5 || settings['project_token'].length > 6
76
+ if !valid_token?(settings['project_token'])
77
+ errors.push("Project token #{settings['project_token']} is not valid.")
78
+ end
79
+ if settings.has_key?('ignore_paths') && !settings['ignore_paths'].kind_of?(Array)
80
+ errors.push("Ignore Paths #{settings['ignore_paths']} should be Array.")
81
+ end
82
+ if !settings.has_key?('url_pattern') || settings['url_pattern'].length == 0
83
+ errors.push("Url pattern #{settings['url_pattern']} is not valid.")
84
+ end
85
+ if !settings.has_key?('query') || !settings['query'].kind_of?(Array)
86
+ errors.push("query config #{settings['query']} is not valid.")
87
+ end
88
+ if !settings.has_key?('ignore_class') || !settings['ignore_class'].kind_of?(Array)
89
+ errors.push("ignore_class config #{settings['ignore_class']} should be Array.")
90
+ end
91
+ if !settings.has_key?('api_url') || settings['api_url'].length == 0
92
+ errors.push("API URL is not configured.")
93
+ end
94
+ if !settings.has_key?('default_lang') || settings['default_lang'].nil?
95
+ errors.push("Default lang #{settings['default_lang']} is not valid.")
96
+ end
97
+ if !settings.has_key?('supported_langs') || !settings['supported_langs'].kind_of?(Array) || settings['supported_langs'].size < 1
98
+ errors.push("Supported langs configuration is not valid.")
99
+ end
100
+ if !settings.has_key?('custom_lang_aliases') || !settings['custom_lang_aliases'].kind_of?(Hash)
101
+ errors.push("Custom lang aliases is not valid.")
102
+ end
103
+ # log errors
104
+ if errors.length > 0
105
+ valid = false
106
+ errors.each do |e|
107
+ WovnLogger.instance.error(e)
108
+ end
109
+ end
110
+ valid
111
+ end
112
+
113
+ # Returns the settings object, pulling from Rails config the first time this is called
114
+ #
115
+ def settings
116
+ load_settings unless @config_loaded
117
+ @settings
118
+ end
119
+
120
+ # Load Rails config.wovnrb
121
+ #
122
+ def load_settings
123
+ if Object.const_defined?(:Rails) && Rails.configuration.respond_to?(:wovnrb)
124
+ @config_loaded = true
125
+ update_settings(Rails.configuration.wovnrb)
126
+ end
127
+ end
128
+
129
+ def update_settings(new_settings)
130
+ load_settings unless @config_loaded
131
+ if !new_settings.nil?
132
+ @settings.merge!(new_settings.stringify_keys)
133
+ format_settings
134
+ end
135
+ end
136
+
137
+ def format_settings
138
+ if @settings.has_key?('custom_lang_aliases')
139
+ stringify_keys! @settings['custom_lang_aliases']
140
+ end
141
+
142
+ @settings['default_lang'] = Lang.get_code(@settings['default_lang'])
143
+ if !@settings.has_key?('supported_langs')
144
+ @settings['supported_langs'] = [@settings['default_lang']]
145
+ end
146
+
147
+ if @settings.has_key?('user_token') && @settings['project_token'].empty?
148
+ @settings['project_token'] = @settings['user_token']
149
+ end
150
+ @settings.delete('user_token')
151
+
152
+ case @settings['url_pattern']
153
+ when 'path'
154
+ @settings['url_pattern_reg'] = "/(?<lang>[^/.?]+)"
155
+ when 'query'
156
+ @settings['url_pattern_reg'] = "((\\?.*&)|\\?)#{@settings['lang_param_name']}=(?<lang>[^&]+)(&|$)"
157
+ when 'subdomain'
158
+ @settings['url_pattern_reg'] = '^(?<lang>[^.]+)\.'
159
+ when 'custom_domain'
160
+ # Do not use regex
161
+ end
162
+
163
+ @settings['test_mode'] = !(@settings['test_mode'] != true || @settings['test_mode'] != 'on')
164
+
165
+ if @settings['wovn_dev_mode']
166
+ if @settings['api_url'] == self.class.default_settings['api_url']
167
+ @settings['api_url'] = 'http://dev-wovn.io:3001'
168
+ end
169
+
170
+ if @settings['api_timeout_seconds'] == self.class.default_settings['api_timeout_seconds']
171
+ @settings['api_timeout_seconds'] = 3.0
172
+ end
173
+ end
174
+ end
175
+
176
+ def custom_lang_aliases
177
+ @settings['custom_lang_aliases'] || {}
178
+ end
179
+
180
+ def default_lang
181
+ @settings['default_lang']
182
+ end
183
+
184
+ def default_lang_alias
185
+ custom_alias = custom_lang_aliases[default_lang]
186
+ custom_alias || default_lang
187
+ end
188
+
189
+ def supported_langs
190
+ @settings['supported_langs'] || []
191
+ end
192
+
193
+ def compress_api_requests?
194
+ @settings['compress_api_requests']
195
+ end
196
+
197
+ def widget_url
198
+ @settings['widget_url'] || 'https://j.wovn.io/1'
199
+ end
200
+
201
+ def dev_mode?
202
+ @settings['wovn_dev_mode']
203
+ end
204
+
205
+ def url_pattern
206
+ @settings['url_pattern']
207
+ end
208
+
209
+ def custom_domain_langs
210
+ @custom_domain_langs ||= CustomDomainLangs.new(@settings['custom_domain_langs'])
211
+ end
212
+
213
+ private
214
+
215
+ def stringify_keys!(hash)
216
+ hash.keys.each do |k|
217
+ hash[k.to_s] = hash.delete(k)
218
+ end
219
+ end
220
+ end
221
+ end
@@ -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
- module Wovnrb
2
- VERSION = '3.8.0'.freeze
3
- end
1
+ module Wovnrb
2
+ VERSION = '3.10.0'.freeze
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