locomotivecms_steam 1.5.0.beta3 → 1.5.0.rc0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +0 -4
- data/Gemfile.lock +3 -6
- data/README.md +33 -0
- data/Rakefile +8 -3
- data/docker-compose.yml +15 -0
- data/lib/locomotive/steam/entities/content_type.rb +7 -2
- data/lib/locomotive/steam/liquid/drops/section_content_proxy.rb +4 -0
- data/lib/locomotive/steam/liquid/filters/translate.rb +2 -0
- data/lib/locomotive/steam/liquid/tags/link_to.rb +8 -1
- data/lib/locomotive/steam/liquid/tags/model_form.rb +7 -1
- data/lib/locomotive/steam/middlewares/auth.rb +10 -17
- data/lib/locomotive/steam/middlewares/concerns/auth_helpers.rb +30 -0
- data/lib/locomotive/steam/middlewares/concerns/helpers.rb +13 -3
- data/lib/locomotive/steam/middlewares/concerns/liquid_context.rb +1 -1
- data/lib/locomotive/steam/middlewares/concerns/recaptcha.rb +30 -0
- data/lib/locomotive/steam/middlewares/default_env.rb +5 -3
- data/lib/locomotive/steam/middlewares/entry_submission.rb +4 -1
- data/lib/locomotive/steam/middlewares/impersonated_entry.rb +73 -0
- data/lib/locomotive/steam/middlewares/locale.rb +14 -9
- data/lib/locomotive/steam/models/associations/belongs_to.rb +6 -0
- data/lib/locomotive/steam/server.rb +1 -0
- data/lib/locomotive/steam/services.rb +9 -1
- data/lib/locomotive/steam/services/action_service.rb +3 -3
- data/lib/locomotive/steam/services/auth_service.rb +1 -1
- data/lib/locomotive/steam/services/content_entry_service.rb +11 -1
- data/lib/locomotive/steam/services/cookie_service.rb +25 -0
- data/lib/locomotive/steam/services/page_finder_service.rb +12 -0
- data/lib/locomotive/steam/services/recaptcha_service.rb +33 -0
- data/lib/locomotive/steam/services/translator_service.rb +12 -13
- data/lib/locomotive/steam/version.rb +1 -1
- data/spec/integration/liquid/filters/translate_spec.rb +7 -0
- data/spec/integration/repositories/content_entry_repository_spec.rb +4 -0
- data/spec/integration/server/auth_spec.rb +8 -1
- data/spec/integration/services/content_entry_service_spec.rb +11 -0
- data/spec/support/helpers.rb +1 -1
- data/spec/unit/liquid/drops/section_content_proxy_spec.rb +8 -0
- data/spec/unit/liquid/tags/action_spec.rb +2 -1
- data/spec/unit/liquid/tags/global_section_spec.rb +2 -1
- data/spec/unit/liquid/tags/link_to_spec.rb +7 -0
- data/spec/unit/liquid/tags/model_form_spec.rb +7 -0
- data/spec/unit/liquid/tags/section_spec.rb +2 -1
- data/spec/unit/liquid/tags/snippet_spec.rb +2 -1
- data/spec/unit/middlewares/auth_spec.rb +1 -0
- data/spec/unit/middlewares/entry_submission_spec.rb +64 -20
- data/spec/unit/middlewares/helpers_spec.rb +5 -1
- data/spec/unit/middlewares/impersonated_entry_spec.rb +80 -0
- data/spec/unit/middlewares/locale_spec.rb +57 -36
- data/spec/unit/middlewares/section_spec.rb +6 -5
- data/spec/unit/middlewares/site_spec.rb +13 -0
- data/spec/unit/services/action_service_spec.rb +17 -11
- data/spec/unit/services/auth_service_spec.rb +7 -1
- data/spec/unit/services/content_entry_service_spec.rb +25 -4
- data/spec/unit/services/cookie_service_spec.rb +38 -0
- data/spec/unit/services/page_finder_service_spec.rb +69 -0
- data/spec/unit/services/recaptcha_service_spec.rb +71 -0
- data/spec/unit/services/translator_service_spec.rb +11 -1
- metadata +16 -2
@@ -10,7 +10,7 @@ module Locomotive::Steam
|
|
10
10
|
# /index => locale = :en (default one)
|
11
11
|
#
|
12
12
|
# /en/index?locale=fr => locale = :fr
|
13
|
-
# /index => redirection to /en if the locale in
|
13
|
+
# /index => redirection to /en if the locale in cookie is :en
|
14
14
|
#
|
15
15
|
class Locale < ThreadSafe
|
16
16
|
|
@@ -19,7 +19,9 @@ module Locomotive::Steam
|
|
19
19
|
def _call
|
20
20
|
env['steam.path'] = request.path_info
|
21
21
|
|
22
|
-
env['steam.locale'] =
|
22
|
+
env['steam.locale'] = services.locale = extract_locale
|
23
|
+
|
24
|
+
set_locale_cookie
|
23
25
|
|
24
26
|
log "Locale used: #{locale.upcase}"
|
25
27
|
|
@@ -34,7 +36,7 @@ module Locomotive::Steam
|
|
34
36
|
# Regarding the index page (basically, "/"), we've to see if we could
|
35
37
|
# guess the locale from the headers the browser sends to us.
|
36
38
|
locale = if is_index_page?
|
37
|
-
locale_from_params ||
|
39
|
+
locale_from_params || locale_from_cookie || locale_from_header
|
38
40
|
else
|
39
41
|
locale_from_path || locale_from_params
|
40
42
|
end
|
@@ -75,20 +77,23 @@ module Locomotive::Steam
|
|
75
77
|
end
|
76
78
|
end
|
77
79
|
|
78
|
-
def
|
79
|
-
if locale =
|
80
|
-
env['steam.locale_in_session'] = true
|
80
|
+
def locale_from_cookie
|
81
|
+
if locale = services.cookie.get(cookie_key_name)
|
81
82
|
|
82
|
-
log 'Locale extracted from the
|
83
|
+
log 'Locale extracted from the cookie'
|
83
84
|
|
84
85
|
locale.to_sym
|
85
86
|
end
|
86
87
|
end
|
87
88
|
|
88
|
-
|
89
|
+
def set_locale_cookie
|
90
|
+
services.cookie.set(cookie_key_name, {'value': locale, 'path': '/', 'max_age': 1.year})
|
91
|
+
end
|
92
|
+
|
93
|
+
# The preview urls for all the sites share the same domain, so cookie[:locale]
|
89
94
|
# would be the same for all the preview urls and this is not good.
|
90
95
|
# This is why we need to use a different key.
|
91
|
-
def
|
96
|
+
def cookie_key_name
|
92
97
|
live_editing? ? "steam-locale-#{site.handle}" : 'steam-locale'
|
93
98
|
end
|
94
99
|
|
@@ -75,7 +75,7 @@ module Locomotive
|
|
75
75
|
end
|
76
76
|
|
77
77
|
register :action do
|
78
|
-
Steam::ActionService.new(current_site, email, content_entry: content_entry, api: external_api, redirection: page_redirection)
|
78
|
+
Steam::ActionService.new(current_site, email, content_entry: content_entry, api: external_api, redirection: page_redirection, cookie: cookie)
|
79
79
|
end
|
80
80
|
|
81
81
|
register :content_entry do
|
@@ -142,10 +142,18 @@ module Locomotive
|
|
142
142
|
Steam::AuthService.new(current_site, content_entry, email)
|
143
143
|
end
|
144
144
|
|
145
|
+
register :recaptcha do
|
146
|
+
Steam::RecaptchaService.new(current_site, request)
|
147
|
+
end
|
148
|
+
|
145
149
|
register :cache do
|
146
150
|
Steam::NoCacheService.new
|
147
151
|
end
|
148
152
|
|
153
|
+
register :cookie do
|
154
|
+
Steam::CookieService.new(request)
|
155
|
+
end
|
156
|
+
|
149
157
|
register :configuration do
|
150
158
|
Locomotive::Steam.configuration
|
151
159
|
end
|
@@ -11,7 +11,7 @@ module Locomotive
|
|
11
11
|
|
12
12
|
class ActionService
|
13
13
|
|
14
|
-
SERVICES = %w(content_entry api redirection)
|
14
|
+
SERVICES = %w(content_entry api redirection cookie)
|
15
15
|
|
16
16
|
BUILT_IN_FUNCTIONS = %w(
|
17
17
|
getProp
|
@@ -84,11 +84,11 @@ module Locomotive
|
|
84
84
|
end
|
85
85
|
|
86
86
|
def get_cookies_prop_lambda(liquid_context)
|
87
|
-
-> (name) {
|
87
|
+
-> (name) { cookie_service.get(name.to_s) }
|
88
88
|
end
|
89
89
|
|
90
90
|
def set_cookies_prop_lambda(liquid_context)
|
91
|
-
-> (name,
|
91
|
+
-> (name, values) { cookie_service.set(name.to_s, values) }
|
92
92
|
end
|
93
93
|
|
94
94
|
def all_entries_lambda(liquid_context)
|
@@ -30,7 +30,7 @@ module Locomotive
|
|
30
30
|
def sign_in(options, request)
|
31
31
|
entry = entries.all(options.type, options.id_field => options.id).first
|
32
32
|
|
33
|
-
if entry && entry.send(options.password_field)
|
33
|
+
if entry && (entry.send(options.password_field).present?)
|
34
34
|
hashed_password = entry[:"#{options.password_field}_hash"]
|
35
35
|
password = ::BCrypt::Engine.hash_secret(options.password, entry.send(options.password_field).try(:salt))
|
36
36
|
same_password = secure_compare(password, hashed_password)
|
@@ -24,6 +24,16 @@ module Locomotive
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
+
def build(type_slug, attributes)
|
28
|
+
with_repository(type_slug) do |_repository|
|
29
|
+
_attributes = prepare_attributes(_repository.content_type, attributes)
|
30
|
+
|
31
|
+
entry = _repository.build(_attributes)
|
32
|
+
|
33
|
+
i18n_decorate { entry }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
27
37
|
# Warning: do not work with localized and file fields
|
28
38
|
def create(type_slug, attributes, as_json = false)
|
29
39
|
with_repository(type_slug) do |_repository|
|
@@ -144,7 +154,7 @@ module Locomotive
|
|
144
154
|
# check if the entry has unique values for its
|
145
155
|
# fields marked as unique
|
146
156
|
content_type_repository.look_for_unique_fields(entry.content_type).each do |name, _|
|
147
|
-
if _repository.exists?(name => entry.send(name))
|
157
|
+
if _repository.exists?(name => entry.send(name), :"_id.ne" => entry._id)
|
148
158
|
entry.errors.add(name, :taken)
|
149
159
|
end
|
150
160
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Locomotive
|
2
|
+
module Steam
|
3
|
+
|
4
|
+
class CookieService
|
5
|
+
|
6
|
+
def initialize(request)
|
7
|
+
@request = request
|
8
|
+
@cookies = request.env['steam.cookies']
|
9
|
+
end
|
10
|
+
|
11
|
+
def set(key, vals)
|
12
|
+
@cookies[key] = vals
|
13
|
+
end
|
14
|
+
|
15
|
+
def get(key)
|
16
|
+
if @cookies.include?(key)
|
17
|
+
@cookies[key]['value']
|
18
|
+
else
|
19
|
+
@request.cookies[key]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -16,6 +16,11 @@ module Locomotive
|
|
16
16
|
def match(path)
|
17
17
|
decorate do
|
18
18
|
repository.matching_fullpath(path_combinations(path))
|
19
|
+
end.sort do |page_1, page_2|
|
20
|
+
# normal pages have priority over the templatized ones if they're not in the same "folder"
|
21
|
+
same_folder?(page_1, page_2) ?
|
22
|
+
page_1.position <=> page_2.position :
|
23
|
+
(page_2.fullpath.include?(WILDCARD) ? 0 : 1) <=> (page_1 .fullpath.include?(WILDCARD) ? 0 : 1)
|
19
24
|
end
|
20
25
|
end
|
21
26
|
|
@@ -58,6 +63,13 @@ module Locomotive
|
|
58
63
|
_path_combinations(path.split('/'))
|
59
64
|
end
|
60
65
|
|
66
|
+
def same_folder?(page_1, page_2)
|
67
|
+
(path_1 = page_1.fullpath.split('/')).pop
|
68
|
+
(path_2 = page_2.fullpath.split('/')).pop
|
69
|
+
|
70
|
+
path_1.join('/') == path_2.join('/')
|
71
|
+
end
|
72
|
+
|
61
73
|
def _path_combinations(segments, can_include_template = true)
|
62
74
|
return nil if segments.empty?
|
63
75
|
segment = segments.shift
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
|
3
|
+
module Locomotive
|
4
|
+
module Steam
|
5
|
+
|
6
|
+
# This service supports Google Recaptcha or any API compatible with Google
|
7
|
+
class RecaptchaService
|
8
|
+
|
9
|
+
GOOGLE_API_URL = 'https://www.google.com/recaptcha/api/siteverify'.freeze
|
10
|
+
|
11
|
+
def initialize(site, request)
|
12
|
+
@api = site.metafields.dig(:google, :recaptcha_api_url) || GOOGLE_API_URL
|
13
|
+
@secret = site.metafields.dig(:google, :recaptcha_secret)
|
14
|
+
@ip = request.ip
|
15
|
+
end
|
16
|
+
|
17
|
+
def verify(response_code)
|
18
|
+
# save a HTTP query if there is no code
|
19
|
+
return false if response_code.blank?
|
20
|
+
|
21
|
+
_response = HTTParty.get(@api, { query: {
|
22
|
+
secret: @secret,
|
23
|
+
response: response_code,
|
24
|
+
remoteip: @ip
|
25
|
+
}})
|
26
|
+
|
27
|
+
_response.parsed_response['success']
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -15,23 +15,22 @@ module Locomotive
|
|
15
15
|
def translate(input, options = {})
|
16
16
|
locale = options['locale'] || self.current_locale
|
17
17
|
scope = options.delete('scope')
|
18
|
+
key = scope.present? ? "#{scope.gsub('.', '_')}_#{input}" : input
|
18
19
|
|
19
|
-
if
|
20
|
-
input = "#{input}_#{pluralize_prefix(options['count'])}" if options['count']
|
20
|
+
key = "#{key}_#{pluralize_prefix(options['count'])}" if options['count']
|
21
21
|
|
22
|
-
|
22
|
+
values = find_values_by_key(key)
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
ActiveSupport::Notifications.instrument('steam.missing_translation', input: input, locale: locale)
|
31
|
-
input
|
32
|
-
end
|
24
|
+
# FIXME: important to check if the returned value is nil (instead of nil + false)
|
25
|
+
# false being reserved for an existing key but without provided translation)
|
26
|
+
if (translation = values[locale.to_s]).present?
|
27
|
+
_translate(translation, options)
|
28
|
+
elsif output = I18n.t(input, scope: scope&.split('.'), locale: locale, default: nil)
|
29
|
+
output
|
33
30
|
else
|
34
|
-
|
31
|
+
Locomotive::Common::Logger.warn "Missing translation '#{input}' for the '#{locale}' locale".yellow
|
32
|
+
ActiveSupport::Notifications.instrument('steam.missing_translation', input: input, locale: locale)
|
33
|
+
input
|
35
34
|
end
|
36
35
|
end
|
37
36
|
|
@@ -48,6 +48,13 @@ describe Locomotive::Steam::Liquid::Filters::Translate do
|
|
48
48
|
|
49
49
|
end
|
50
50
|
|
51
|
+
describe 'shortcut alias' do
|
52
|
+
|
53
|
+
let(:source) { "{{ 'welcome_message' | t: 'fr', 'locomotive.default' }}" }
|
54
|
+
it { expect(translator).to receive(:translate).with('welcome_message', 'locale' => 'fr', 'scope' => 'locomotive.default'); subject }
|
55
|
+
|
56
|
+
end
|
57
|
+
|
51
58
|
end
|
52
59
|
|
53
60
|
describe 'pluralization' do
|
@@ -79,6 +79,10 @@ describe Locomotive::Steam::ContentEntryRepository do
|
|
79
79
|
before { target_repository.scope.locale = :fr }
|
80
80
|
subject { target_repository.all(band: nil) }
|
81
81
|
it { expect(subject.size).to eq 2 }
|
82
|
+
|
83
|
+
describe 'set a nil value to the attribute storing the id of the related element' do
|
84
|
+
it { expect(subject.map(&:band_id)).to eq([nil, nil]) }
|
85
|
+
end
|
82
86
|
end
|
83
87
|
end
|
84
88
|
|
@@ -152,9 +152,16 @@ describe 'Authentication' do
|
|
152
152
|
'authenticated_entry_id' => 'john'
|
153
153
|
} }
|
154
154
|
|
155
|
+
it 'redirect to the requested page' do
|
156
|
+
post '/account/sign-in', params, { 'rack.session' => rack_session }
|
157
|
+
expect(last_response.status).to eq 301
|
158
|
+
expect(last_response.location).to eq "/account/sign-in"
|
159
|
+
end
|
160
|
+
|
155
161
|
it 'displays the profile page as described in the params' do
|
156
162
|
post '/account/sign-in', params, { 'rack.session' => rack_session }
|
157
|
-
|
163
|
+
follow_redirect!
|
164
|
+
expect(last_response.body).to include "Sign in"
|
158
165
|
expect(last_response.body).not_to include "You're already authenticated!"
|
159
166
|
end
|
160
167
|
|
@@ -76,6 +76,17 @@ describe Locomotive::Steam::ContentEntryService do
|
|
76
76
|
|
77
77
|
after(:all) { Locomotive::Steam::Adapters::Filesystem::SimpleCacheStore.new.clear }
|
78
78
|
|
79
|
+
describe '#build' do
|
80
|
+
|
81
|
+
let(:attributes) { { name: 'John', email: 'john@doe.net', message: 'Hello world!' } }
|
82
|
+
|
83
|
+
subject { service.build('messages', attributes) }
|
84
|
+
|
85
|
+
it { expect(subject['name']).to eq 'John' }
|
86
|
+
it { expect(subject['errors'].blank?).to eq true }
|
87
|
+
|
88
|
+
end
|
89
|
+
|
79
90
|
describe '#create' do
|
80
91
|
|
81
92
|
let(:attributes) { { name: 'John', email: 'john@doe.net', message: 'Hello world!' } }
|
data/spec/support/helpers.rb
CHANGED
@@ -46,6 +46,14 @@ describe Locomotive::Steam::Liquid::Drops::SectionContentProxy do
|
|
46
46
|
|
47
47
|
end
|
48
48
|
|
49
|
+
context 'it outputs the target="_blank" A attribute if new window is true' do
|
50
|
+
|
51
|
+
subject { drop.before_method(:link).new_window_attribute }
|
52
|
+
|
53
|
+
it { is_expected.to eq('target="_blank"') }
|
54
|
+
|
55
|
+
end
|
56
|
+
|
49
57
|
end
|
50
58
|
|
51
59
|
describe 'image picker type setting' do
|
@@ -6,7 +6,8 @@ describe Locomotive::Steam::Liquid::Tags::Action do
|
|
6
6
|
let(:source) { '{% action "random Javascript action" %}var foo = 42; setProp("foo", foo);{% endaction %}' }
|
7
7
|
let(:assigns) { {} }
|
8
8
|
let(:registers) { { services: services } }
|
9
|
-
let(:
|
9
|
+
let(:request) { instance_double('Request', env: {}) }
|
10
|
+
let(:services) { Locomotive::Steam::Services.build_instance(request) }
|
10
11
|
let(:context) { ::Liquid::Context.new(assigns, {}, registers) }
|
11
12
|
|
12
13
|
before { allow(services).to receive(:current_site).and_return(site) }
|
@@ -2,7 +2,8 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Locomotive::Steam::Liquid::Tags::GlobalSection do
|
4
4
|
|
5
|
-
let(:
|
5
|
+
let(:request) { instance_double('Request', env: {}) }
|
6
|
+
let(:services) { Locomotive::Steam::Services.build_instance(request) }
|
6
7
|
let(:finder) { services.section_finder }
|
7
8
|
let(:source) { 'Locomotive {% global_section header %}' }
|
8
9
|
let(:live_editing) { true }
|
@@ -75,6 +75,13 @@ describe Locomotive::Steam::Liquid::Tags::PathTo do
|
|
75
75
|
|
76
76
|
end
|
77
77
|
|
78
|
+
context 'with a custom CSS class' do
|
79
|
+
|
80
|
+
let(:source) { "{% link_to index, locale: 'fr', class: 'button' %}" }
|
81
|
+
it { is_expected.to eq '<a href="/fr" class="button">Accueil</a>' }
|
82
|
+
|
83
|
+
end
|
84
|
+
|
78
85
|
end
|
79
86
|
|
80
87
|
describe 'used as a block' do
|
@@ -34,6 +34,13 @@ describe Locomotive::Steam::Liquid::Tags::ModelForm do
|
|
34
34
|
|
35
35
|
end
|
36
36
|
|
37
|
+
describe 'with recaptcha' do
|
38
|
+
|
39
|
+
let(:source) { "{% model_form 'newsletter_addresses', recaptcha: true %}Newsletter Form{% endmodel_form %}" }
|
40
|
+
it { is_expected.to include %(<input type="hidden" name="g-recaptcha-response" id="g-recaptcha-response" />) }
|
41
|
+
|
42
|
+
end
|
43
|
+
|
37
44
|
describe 'json enabled' do
|
38
45
|
|
39
46
|
let(:source) { "{% model_form 'newsletter_addresses', json: true %}Newsletter Form{% endmodel_form %}" }
|