locomotivecms_steam 1.5.0.rc0 → 1.5.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -1
  3. data/Gemfile +3 -6
  4. data/Gemfile.lock +39 -39
  5. data/README.md +2 -2
  6. data/lib/locomotive/steam/adapters/filesystem/sanitizers/section.rb +62 -16
  7. data/lib/locomotive/steam/adapters/filesystem/sanitizers/site.rb +15 -1
  8. data/lib/locomotive/steam/adapters/filesystem/yaml_loader.rb +30 -9
  9. data/lib/locomotive/steam/adapters/filesystem/yaml_loaders/content_entry.rb +1 -1
  10. data/lib/locomotive/steam/adapters/filesystem/yaml_loaders/page.rb +1 -1
  11. data/lib/locomotive/steam/adapters/filesystem/yaml_loaders/section.rb +14 -2
  12. data/lib/locomotive/steam/adapters/filesystem/yaml_loaders/site.rb +1 -1
  13. data/lib/locomotive/steam/adapters/filesystem/yaml_loaders/translation.rb +1 -1
  14. data/lib/locomotive/steam/adapters/mongodb.rb +1 -1
  15. data/lib/locomotive/steam/entities/content_entry.rb +0 -1
  16. data/lib/locomotive/steam/entities/site.rb +4 -0
  17. data/lib/locomotive/steam/errors.rb +54 -18
  18. data/lib/locomotive/steam/liquid/drops/content_entry.rb +1 -1
  19. data/lib/locomotive/steam/liquid/drops/content_entry_collection.rb +1 -1
  20. data/lib/locomotive/steam/liquid/drops/content_types.rb +1 -1
  21. data/lib/locomotive/steam/liquid/drops/inherited_block.rb +28 -0
  22. data/lib/locomotive/steam/liquid/drops/metafields.rb +2 -2
  23. data/lib/locomotive/steam/liquid/drops/params.rb +1 -1
  24. data/lib/locomotive/steam/liquid/drops/section.rb +10 -2
  25. data/lib/locomotive/steam/liquid/drops/section_content_proxy.rb +13 -2
  26. data/lib/locomotive/steam/liquid/drops/section_editor_setting_data.rb +1 -1
  27. data/lib/locomotive/steam/liquid/drops/session_proxy.rb +1 -1
  28. data/lib/locomotive/steam/liquid/file_system.rb +46 -0
  29. data/lib/locomotive/steam/liquid/filters/array.rb +61 -0
  30. data/lib/locomotive/steam/liquid/filters/misc.rb +12 -2
  31. data/lib/locomotive/steam/liquid/filters/number.rb +13 -12
  32. data/lib/locomotive/steam/liquid/filters/text.rb +4 -0
  33. data/lib/locomotive/steam/liquid/patches.rb +58 -19
  34. data/lib/locomotive/steam/liquid/tags/alt_page_links.rb +9 -5
  35. data/lib/locomotive/steam/liquid/tags/concerns/attributes.rb +47 -0
  36. data/lib/locomotive/steam/liquid/tags/concerns/path.rb +18 -31
  37. data/lib/locomotive/steam/liquid/tags/concerns/section.rb +22 -11
  38. data/lib/locomotive/steam/liquid/tags/consume.rb +26 -33
  39. data/lib/locomotive/steam/liquid/tags/csrf.rb +2 -2
  40. data/lib/locomotive/steam/liquid/tags/editable/base.rb +30 -20
  41. data/lib/locomotive/steam/liquid/tags/editable/control.rb +2 -2
  42. data/lib/locomotive/steam/liquid/tags/editable/file.rb +11 -11
  43. data/lib/locomotive/steam/liquid/tags/editable/text.rb +5 -5
  44. data/lib/locomotive/steam/liquid/tags/extends.rb +56 -8
  45. data/lib/locomotive/steam/liquid/tags/global_section.rb +6 -6
  46. data/lib/locomotive/steam/liquid/tags/google_analytics.rb +16 -6
  47. data/lib/locomotive/steam/liquid/tags/hybrid.rb +8 -4
  48. data/lib/locomotive/steam/liquid/tags/inherited_block.rb +90 -13
  49. data/lib/locomotive/steam/liquid/tags/inline_editor.rb +4 -4
  50. data/lib/locomotive/steam/liquid/tags/link_to.rb +2 -1
  51. data/lib/locomotive/steam/liquid/tags/locale_switcher.rb +25 -21
  52. data/lib/locomotive/steam/liquid/tags/model_form.rb +38 -17
  53. data/lib/locomotive/steam/liquid/tags/nav.rb +4 -4
  54. data/lib/locomotive/steam/liquid/tags/page_not_found.rb +19 -0
  55. data/lib/locomotive/steam/liquid/tags/paginate.rb +13 -7
  56. data/lib/locomotive/steam/liquid/tags/path_to.rb +1 -0
  57. data/lib/locomotive/steam/liquid/tags/redirect_to.rb +34 -0
  58. data/lib/locomotive/steam/liquid/tags/section.rb +34 -33
  59. data/lib/locomotive/steam/liquid/tags/sections_dropzone.rb +1 -1
  60. data/lib/locomotive/steam/liquid/tags/seo.rb +2 -4
  61. data/lib/locomotive/steam/liquid/tags/session_assign.rb +1 -0
  62. data/lib/locomotive/steam/liquid/tags/snippet.rb +21 -29
  63. data/lib/locomotive/steam/liquid/tags/with_scope.rb +61 -27
  64. data/lib/locomotive/steam/liquid.rb +3 -1
  65. data/lib/locomotive/steam/middlewares/cache.rb +117 -0
  66. data/lib/locomotive/steam/middlewares/concerns/helpers.rb +22 -8
  67. data/lib/locomotive/steam/middlewares/concerns/liquid_context.rb +5 -1
  68. data/lib/locomotive/steam/middlewares/concerns/rendering.rb +53 -0
  69. data/lib/locomotive/steam/middlewares/impersonated_entry.rb +4 -0
  70. data/lib/locomotive/steam/middlewares/locale.rb +2 -2
  71. data/lib/locomotive/steam/middlewares/locale_redirection.rb +1 -1
  72. data/lib/locomotive/steam/middlewares/logging.rb +1 -0
  73. data/lib/locomotive/steam/middlewares/page_not_found.rb +37 -0
  74. data/lib/locomotive/steam/middlewares/redirection.rb +1 -1
  75. data/lib/locomotive/steam/middlewares/renderer.rb +4 -26
  76. data/lib/locomotive/steam/middlewares/thread_safe.rb +0 -4
  77. data/lib/locomotive/steam/models/pager.rb +1 -0
  78. data/lib/locomotive/steam/server.rb +3 -1
  79. data/lib/locomotive/steam/services/action_service.rb +5 -0
  80. data/lib/locomotive/steam/services/auth_service.rb +9 -9
  81. data/lib/locomotive/steam/services/cookie_service.rb +1 -0
  82. data/lib/locomotive/steam/services/external_api_service.rb +5 -0
  83. data/lib/locomotive/steam/services/liquid_parser_service.rb +4 -2
  84. data/lib/locomotive/steam/services/page_finder_service.rb +1 -1
  85. data/lib/locomotive/steam/services/recaptcha_service.rb +4 -2
  86. data/lib/locomotive/steam/version.rb +1 -1
  87. data/lib/locomotive/steam.rb +5 -1
  88. data/locomotivecms_steam.gemspec +4 -4
  89. data/spec/fixtures/default/app/views/pages/basic.liquid.haml +1 -0
  90. data/spec/fixtures/default/app/views/pages/music.liquid.haml +6 -0
  91. data/spec/fixtures/default/app/views/sections/carousel.liquid +15 -16
  92. data/spec/fixtures/default/app/views/sections/footer.liquid +37 -3
  93. data/spec/fixtures/default/app/views/sections/header.liquid +47 -10
  94. data/spec/fixtures/default/app/views/sections/misc/hero.liquid +28 -0
  95. data/spec/fixtures/default/config/metafields_schema.yml +3 -0
  96. data/spec/integration/liquid/tags/section_spec.rb +82 -0
  97. data/spec/integration/repositories/content_entry_repository_spec.rb +9 -0
  98. data/spec/integration/server/basic_spec.rb +2 -2
  99. data/spec/integration/server/metafields_spec.rb +1 -0
  100. data/spec/integration/services/content_entry_service_spec.rb +12 -0
  101. data/spec/support/helpers.rb +1 -0
  102. data/spec/support/liquid.rb +32 -2
  103. data/spec/support/mongo.rb +1 -0
  104. data/spec/unit/adapters/filesystem/sanitizers/section_spec.rb +66 -40
  105. data/spec/unit/adapters/filesystem/yaml_loaders/section_spec.rb +25 -0
  106. data/spec/unit/errors_spec.rb +1 -1
  107. data/spec/unit/liquid/drops/content_entry_collection_spec.rb +3 -3
  108. data/spec/unit/liquid/drops/content_entry_spec.rb +4 -4
  109. data/spec/unit/liquid/drops/content_types_spec.rb +2 -2
  110. data/spec/unit/liquid/drops/metafields_spec.rb +8 -8
  111. data/spec/unit/liquid/drops/params_spec.rb +5 -5
  112. data/spec/unit/liquid/drops/section_content_proxy_spec.rb +69 -18
  113. data/spec/unit/liquid/drops/section_spec.rb +1 -1
  114. data/spec/unit/liquid/file_system_spec.rb +25 -0
  115. data/spec/unit/liquid/filters/array_spec.rb +140 -0
  116. data/spec/unit/liquid/filters/misc_spec.rb +21 -3
  117. data/spec/unit/liquid/filters/number_spec.rb +4 -4
  118. data/spec/unit/liquid/filters/text_spec.rb +4 -0
  119. data/spec/unit/liquid/tags/alt_page_links_spec.rb +19 -2
  120. data/spec/unit/liquid/tags/authorize_spec.rb +1 -1
  121. data/spec/unit/liquid/tags/editable/text_spec.rb +32 -4
  122. data/spec/unit/liquid/tags/extends_spec.rb +115 -28
  123. data/spec/unit/liquid/tags/global_section_spec.rb +4 -3
  124. data/spec/unit/liquid/tags/google_analytics_spec.rb +21 -2
  125. data/spec/unit/liquid/tags/inherited_block_spec.rb +18 -4
  126. data/spec/unit/liquid/tags/inline_editor_spec.rb +11 -0
  127. data/spec/unit/liquid/tags/link_to_spec.rb +1 -1
  128. data/spec/unit/liquid/tags/model_form_spec.rb +7 -0
  129. data/spec/unit/liquid/tags/page_not_found_spec.rb +14 -0
  130. data/spec/unit/liquid/tags/redirect_to_spec.rb +171 -0
  131. data/spec/unit/liquid/tags/section_spec.rb +43 -3
  132. data/spec/unit/liquid/tags/sections_dropzone_spec.rb +0 -1
  133. data/spec/unit/liquid/tags/snippet_spec.rb +9 -8
  134. data/spec/unit/liquid/tags/with_scope_spec.rb +80 -60
  135. data/spec/unit/middlewares/cache_spec.rb +186 -0
  136. data/spec/unit/middlewares/impersonated_entry_spec.rb +7 -0
  137. data/spec/unit/middlewares/locale_redirection_spec.rb +7 -0
  138. data/spec/unit/middlewares/locale_spec.rb +8 -1
  139. data/spec/unit/middlewares/page_not_found_spec.rb +46 -0
  140. data/spec/unit/middlewares/redirection_spec.rb +8 -0
  141. data/spec/unit/middlewares/renderer_spec.rb +64 -6
  142. data/spec/unit/middlewares/section_spec.rb +1 -0
  143. data/spec/unit/models/pager_spec.rb +11 -1
  144. data/spec/unit/repositories/section_repository_spec.rb +1 -1
  145. data/spec/unit/services/action_service_spec.rb +23 -3
  146. data/spec/unit/services/page_redirection_service_spec.rb +2 -2
  147. data/spec/unit/services/recaptcha_service_spec.rb +1 -1
  148. metadata +50 -24
@@ -5,45 +5,37 @@ module Locomotive
5
5
 
6
6
  class Snippet < ::Liquid::Include
7
7
 
8
- def parse(tokens)
9
- name = evaluate_snippet_name
8
+ attr_reader :name
9
+
10
+ def initialize(tag_name, markup, options)
11
+ super
10
12
 
11
- ActiveSupport::Notifications.instrument('steam.parse.include', page: options[:page], name: name)
13
+ # we use a convention to differentiate sections from snippets
14
+ @name = evaluate_snippet_name
15
+ @template_name_expr = "snippets--#{@name}"
16
+ end
17
+
18
+ def parse(tokens)
19
+ ActiveSupport::Notifications.instrument('steam.parse.include', page: parse_context[:page], name: name)
12
20
 
13
- # look for editable elements
14
- if options[:snippet_finder] && snippet = options[:snippet_finder].find(name)
15
- options[:parser]._parse(snippet, options.merge(snippet: name))
21
+ # look for editable elements (only used by the Engine)
22
+ # In the next version of Locomotive (v5), we won't support the editable elements
23
+ if parse_context[:snippet_finder] && snippet = parse_context[:snippet_finder].find(name)
24
+ parse_context[:parser]._parse(snippet, parse_context.merge(snippet: name))
16
25
  end
17
26
  end
18
27
 
19
28
  def render(context)
20
- @template_name = evaluate_snippet_name(context)
21
- # @options doesn't include the page key if cache is on
22
- @options[:page] = context.registers[:page]
23
-
24
- begin
25
- super
26
- rescue Locomotive::Steam::ParsingRenderingError => e
27
- e.file = @template_name + ' [Snippet]'
28
- raise e
29
- end
29
+ # parse_context (previously @options) doesn't include the page key if cache is on
30
+ parse_context[:page] = context.registers[:page]
31
+ super
30
32
  end
31
33
 
32
34
  private
33
35
 
34
- def read_template_from_file_system(context)
35
- service = context.registers[:services]
36
- snippet = service.snippet_finder.find(@template_name)
37
-
38
- raise SnippetNotFound.new("Snippet with slug '#{@template_name}' was not found") if snippet.nil?
39
-
40
- snippet.liquid_source
41
- end
42
-
43
- def evaluate_snippet_name(context = nil)
44
- context.try(:evaluate, @template_name) ||
45
- (!@template_name.is_a?(String) && @template_name.send(:state).first) ||
46
- @template_name
36
+ def evaluate_snippet_name
37
+ (!template_name_expr.is_a?(String) && template_name_expr.send(:state).first) ||
38
+ template_name_expr
47
39
  end
48
40
 
49
41
  end
@@ -14,65 +14,99 @@ module Locomotive
14
14
  # {% endwith_scope %}
15
15
  #
16
16
 
17
- class WithScope < Solid::Block
17
+ class WithScope < ::Liquid::Block
18
18
 
19
- OPERATORS = %w(all exists gt gte in lt lte ne nin size near within)
19
+ include Concerns::Attributes
20
20
 
21
- SYMBOL_OPERATORS_REGEXP = /(\w+\.(#{OPERATORS.join('|')})){1}\s*\:/o
21
+ # Regexps and Arrays are allowed
22
+ ArrayFragment = /\[(\s*(#{::Liquid::QuotedFragment},\s*)*#{::Liquid::QuotedFragment}\s*)\]/o.freeze
23
+ RegexpFragment = /\/([^\/]+)\/([imx]+)?/o.freeze
24
+
25
+ # a slight different from the Shopify implementation because we allow stuff like `started_at.le`
26
+ TagAttributes = /([a-zA-Z_0-9\.]+)\s*\:\s*(#{ArrayFragment}|#{RegexpFragment}|#{::Liquid::QuotedFragment})/o.freeze
22
27
 
23
28
  REGEX_OPTIONS = {
24
29
  'i' => Regexp::IGNORECASE,
25
30
  'm' => Regexp::MULTILINE,
26
31
  'x' => Regexp::EXTENDED
27
- }
32
+ }.freeze
33
+
34
+ attr_reader :attributes
28
35
 
29
- # register the tag
30
- tag_name :with_scope
36
+ def initialize(tag_name, markup, options)
37
+ super
31
38
 
32
- def initialize(name, markup, options)
33
- # convert symbol operators into valid ruby code
34
- markup.gsub!(SYMBOL_OPERATORS_REGEXP, ':"\1" =>')
39
+ parse_attributes(markup) { |value| parse_attribute(value) }
35
40
 
36
- super(name, markup, options)
41
+ if attributes.empty?
42
+ raise ::Liquid::SyntaxError.new("Syntax Error in 'with_scope' - Valid syntax: with_scope <name_1>: <value_1>, ..., <name_n>: <value_n>")
43
+ end
37
44
  end
38
45
 
39
- def display(options = {}, &block)
40
- current_context.stack do
41
- current_context['with_scope'] = self.decode(options)
42
- current_context['with_scope_content_type'] = false # for now, no content type is assigned to this with_scope
43
- yield
46
+ def render(context)
47
+ context.stack do
48
+ context['with_scope'] = self.evaluate_attributes(context)
49
+
50
+ # for now, no content type is assigned to this with_scope
51
+ context['with_scope_content_type'] = false
52
+
53
+ super
44
54
  end
45
55
  end
46
56
 
47
57
  protected
48
58
 
49
- def decode(options)
59
+ def parse_attribute(value)
60
+ case value
61
+ when RegexpFragment
62
+ # let the cast_value attribute create the Regexp (done during the rendering phase)
63
+ value
64
+ when ArrayFragment
65
+ $1.split(',').map { |_value| parse_attribute(_value) }
66
+ else
67
+ ::Liquid::Expression.parse(value)
68
+ end
69
+ end
70
+
71
+ def evaluate_attributes(context)
50
72
  HashWithIndifferentAccess.new.tap do |hash|
51
- options.each do |key, value|
73
+ attributes.each do |key, value|
52
74
  # _slug instead of _permalink
53
75
  _key = key.to_s == '_permalink' ? '_slug' : key.to_s
54
76
 
55
- hash[_key] = cast_value(value)
77
+ # evaluate the value if possible before casting it
78
+ _value = value.is_a?(::Liquid::VariableLookup) ? context.evaluate(value) : value
79
+
80
+ hash[_key] = cast_value(context, _value)
56
81
  end
57
82
  end
58
83
  end
59
84
 
60
- def cast_value(value)
85
+ def cast_value(context, value)
61
86
  case value
62
- when Array then value.map { |_value| cast_value(_value) }
63
- when /^\/([^\/]*)\/([imx]+)?$/
64
- _value, options_str = $1, $2
65
- options = options_str.blank? ? nil : options_str.split('').uniq.inject(0) do |_options, letter|
66
- _options |= REGEX_OPTIONS[letter]
67
- end
68
- Regexp.new(_value, options)
87
+ when Array then value.map { |_value| cast_value(context, _value) }
88
+ when RegexpFragment then create_regexp($1, $2)
69
89
  else
70
- value.respond_to?(:_id) ? value.send(:_source) : value
90
+ _value = context.evaluate(value)
91
+ _value.respond_to?(:_id) ? _value.send(:_source) : _value
71
92
  end
72
93
  end
73
94
 
95
+ def create_regexp(value, unparsed_options)
96
+ options = unparsed_options.blank? ? nil : unparsed_options.split('').uniq.inject(0) do |_options, letter|
97
+ _options |= REGEX_OPTIONS[letter]
98
+ end
99
+ Regexp.new(value, options)
100
+ end
101
+
102
+ def tag_attributes_regexp
103
+ TagAttributes
104
+ end
105
+
74
106
  end
75
107
 
108
+ ::Liquid::Template.register_tag('with_scope'.freeze, WithScope)
109
+
76
110
  end
77
111
  end
78
112
  end
@@ -1,10 +1,12 @@
1
- require 'solid'
1
+ require 'liquid'
2
2
 
3
3
  require_relative 'liquid/errors'
4
4
  require_relative 'liquid/patches'
5
+ require_relative 'liquid/file_system'
5
6
  require_relative 'liquid/drops/base'
6
7
  require_relative 'liquid/drops/i18n_base'
7
8
  require_relative 'liquid/tags/hybrid'
8
9
  require_relative 'liquid/tags/concerns/section'
10
+ require_relative 'liquid/tags/concerns/attributes'
9
11
  require_relative 'liquid/tags/section'
10
12
  require_relative_all %w(. drops filters tags/concerns tags), 'liquid'
@@ -0,0 +1,117 @@
1
+ module Locomotive::Steam
2
+ module Middlewares
3
+
4
+ class Cache < ThreadSafe
5
+
6
+ include Concerns::Helpers
7
+
8
+ CACHEABLE_RESPONSE_CODES = [200, 301, 404, 410].freeze
9
+
10
+ CACHEABLE_REQUEST_METHODS = %w(GET HEAD).freeze
11
+
12
+ DEFAULT_CACHE_CONTROL = 'max-age=0, s-maxage=3600, public, must-revalidate'.freeze
13
+
14
+ DEFAULT_CACHE_VARY = 'Accept-Language'.freeze
15
+
16
+ NO_CACHE_CONTROL = 'max-age=0, private, must-revalidate'.freeze
17
+
18
+ def _call
19
+ if cacheable?
20
+ key = cache_key
21
+
22
+ # TODO: only for debugging
23
+ # log("HTTP keys: #{env.select { |key, _| key.starts_with?('HTTP_') }}".light_blue)
24
+
25
+ # Test if the ETag or Last Modified has been modified. If not, return a 304 response
26
+ if stale?(key)
27
+ render_response(nil, 304, nil)
28
+ return
29
+ end
30
+
31
+ # we have to tell the CDN (or any proxy) what the expiration & validation strategy are
32
+ env['steam.cache_control'] = cache_control
33
+ env['steam.cache_vary'] = cache_vary
34
+ env['steam.cache_etag'] = key
35
+ env['steam.cache_last_modified'] = site.last_modified_at.httpdate
36
+
37
+ # retrieve the response from the cache.
38
+ # This is useful if no CDN is being used.
39
+ code, headers, _ = response = fetch_cached_response(key)
40
+
41
+ unless CACHEABLE_RESPONSE_CODES.include?(code.to_i)
42
+ env['steam.cache_control'] = headers['Cache-Control'] = NO_CACHE_CONTROL
43
+ env['steam.cache_vary'] = headers['Vary'] = nil
44
+ end
45
+
46
+ # we don't want to render twice the page
47
+ @next_response = response
48
+ else
49
+ env['steam.cache_control'] = NO_CACHE_CONTROL
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def fetch_cached_response(key)
56
+ log("Cache key = #{key.inspect}")
57
+ if marshaled = cache.read(key)
58
+ log('Cache HIT')
59
+ Marshal.load(marshaled)
60
+ else
61
+ log('Cache MISS')
62
+ self.next.tap do |response|
63
+ # cache the HTML for further validations (+ optimization)
64
+ cache.write(key, marshal(response))
65
+ end
66
+ end
67
+ end
68
+
69
+ def cacheable?
70
+ CACHEABLE_REQUEST_METHODS.include?(env['REQUEST_METHOD']) &&
71
+ !live_editing? &&
72
+ site.try(:cache_enabled) &&
73
+ page.try(:cache_enabled) &&
74
+ is_redirect_url?
75
+ end
76
+
77
+ def cache_key
78
+ site, path, query = env['steam.site'], env['PATH_INFO'], env['QUERY_STRING']
79
+ key = "#{Locomotive::Steam::VERSION}/site/#{site._id}/#{site.last_modified_at.to_i}/page/#{path}/#{query}"
80
+ Digest::MD5.hexdigest(key)
81
+ end
82
+
83
+ def cache_control
84
+ page.try(:cache_control).presence || site.try(:cache_control).presence || DEFAULT_CACHE_CONTROL
85
+ end
86
+
87
+ def cache_vary
88
+ page.try(:cache_vary).presence || site.try(:cache_vary).presence || DEFAULT_CACHE_VARY
89
+ end
90
+
91
+ def is_redirect_url?
92
+ return false if page.nil?
93
+ page.try(:redirect_url).blank?
94
+ end
95
+
96
+ def marshal(response)
97
+ code, headers, body = response
98
+
99
+ # only keep string value headers
100
+ _headers = headers.reject { |key, val| !val.respond_to?(:to_str) }
101
+
102
+ Marshal.dump([code, _headers, body])
103
+ end
104
+
105
+ def stale?(key)
106
+ env['HTTP_IF_NONE_MATCH'] == key ||
107
+ env['HTTP_IF_MODIFIED_SINCE'] == site.last_modified_at.httpdate
108
+ end
109
+
110
+ def cache
111
+ services.cache
112
+ end
113
+
114
+ end
115
+
116
+ end
117
+ end
@@ -3,8 +3,19 @@ module Locomotive::Steam
3
3
  module Concerns
4
4
  module Helpers
5
5
 
6
+ HTML_CONTENT_TYPE = 'text/html'.freeze
7
+
8
+ HTML_MIME_TYPES = [nil, 'text/html', 'application/x-www-form-urlencoded', 'multipart/form-data'].freeze
9
+
10
+ CACHE_HEADERS = {
11
+ 'steam.cache_control' => 'Cache-Control',
12
+ 'steam.cache_vary' => 'Vary',
13
+ 'steam.cache_etag' => 'ETag',
14
+ 'steam.cache_last_modified' => 'Last-Modified'
15
+ }.freeze
16
+
6
17
  def html?
7
- [nil, 'text/html', 'application/x-www-form-urlencoded', 'multipart/form-data'].include?(self.request.media_type) &&
18
+ HTML_MIME_TYPES.include?(self.request.media_type) &&
8
19
  !self.request.xhr? &&
9
20
  !self.json?
10
21
  end
@@ -14,13 +25,16 @@ module Locomotive::Steam
14
25
  end
15
26
 
16
27
  def render_response(content, code = 200, type = nil)
28
+ base_headers = { 'Content-Type' => type || HTML_CONTENT_TYPE }
29
+
30
+ CACHE_HEADERS.each do |key, http_name|
31
+ base_headers[http_name] = env[key] if env[key]
32
+ end
33
+
17
34
  _headers = env['steam.headers'] || {}
18
35
  inject_cookies(_headers)
19
- @next_response = [
20
- code,
21
- { 'Content-Type' => type || 'text/html' }.merge(_headers),
22
- [content]
23
- ]
36
+
37
+ @next_response = [code, base_headers.merge(_headers), [content]]
24
38
  end
25
39
 
26
40
  def redirect_to(location, type = 301)
@@ -28,7 +42,7 @@ module Locomotive::Steam
28
42
 
29
43
  self.log "Redirected to #{_location}".blue
30
44
 
31
- headers = { 'Content-Type' => 'text/html', 'Location' => _location }
45
+ headers = { 'Content-Type' => HTML_CONTENT_TYPE, 'Location' => _location }
32
46
  inject_cookies(headers)
33
47
 
34
48
  @next_response = [type, headers, []]
@@ -45,7 +59,7 @@ module Locomotive::Steam
45
59
  path ||= env['steam.path']
46
60
 
47
61
  segments = path.split('/')
48
- yield(segments)
62
+ yield(segments) if block_given?
49
63
  path = segments.join('/')
50
64
 
51
65
  path = '/' if path.blank?
@@ -21,7 +21,11 @@ module Locomotive::Steam
21
21
  live_editing: live_editing?,
22
22
  params: params,
23
23
  session: request.session,
24
- cookies: request.cookies
24
+ cookies: request.cookies,
25
+ file_system: Locomotive::Steam::Liquid::FileSystem.new(
26
+ section_finder: services.section_finder,
27
+ snippet_finder: services.snippet_finder
28
+ )
25
29
  }.merge(env['steam.liquid_registers'])
26
30
  end
27
31
 
@@ -0,0 +1,53 @@
1
+ module Locomotive::Steam
2
+ module Middlewares
3
+ module Concerns
4
+ module Rendering
5
+
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ include Concerns::LiquidContext
10
+ end
11
+
12
+ private
13
+
14
+ def render_page
15
+ content = parse_and_render_liquid
16
+
17
+ # for a better SEO score, it's better to use a CDN host including
18
+ # the main domain name.
19
+ content = replace_asset_host(content) if site.asset_host.present?
20
+
21
+ render_response(content, page.not_found? ? 404 : 200, page.response_type)
22
+ end
23
+
24
+ def render_missing_404
25
+ message = (if locale != default_locale
26
+ "Your 404 page is missing in the #{locale} locale."
27
+ else
28
+ "Your 404 page is missing."
29
+ end) + " Please create it."
30
+
31
+ log "[Warning] #{message}".red
32
+
33
+ render_response(message, 404)
34
+ end
35
+
36
+ def parse_and_render_liquid
37
+ document = services.liquid_parser.parse(page)
38
+ begin
39
+ document.render(liquid_context)
40
+ rescue Locomotive::Steam::TemplateError => e
41
+ e.template_name = page.template_path if e.template_name.blank?
42
+ raise e
43
+ end
44
+ end
45
+
46
+ def replace_asset_host(content)
47
+ content.gsub(ASSET_URL_REGEXP, "\\1#{site.asset_host}/\\3\/\\4\\5")
48
+ end
49
+
50
+ end
51
+ end
52
+ end
53
+ end
@@ -14,6 +14,10 @@ module Locomotive::Steam
14
14
 
15
15
  def _call
16
16
  if params[:impersonating] == 'stop'
17
+
18
+ # notify sign out
19
+ services.auth.notify(:signed_out, request.env['steam.authenticated_entry'], request)
20
+
17
21
  # sign out the current user
18
22
  store_authenticated(nil)
19
23
 
@@ -35,7 +35,7 @@ module Locomotive::Steam
35
35
  def extract_locale
36
36
  # Regarding the index page (basically, "/"), we've to see if we could
37
37
  # guess the locale from the headers the browser sends to us.
38
- locale = if is_index_page?
38
+ locale = if is_index_page? && !site.bypass_browser_locale
39
39
  locale_from_params || locale_from_cookie || locale_from_header
40
40
  else
41
41
  locale_from_path || locale_from_params
@@ -87,7 +87,7 @@ module Locomotive::Steam
87
87
  end
88
88
 
89
89
  def set_locale_cookie
90
- services.cookie.set(cookie_key_name, {'value': locale, 'path': '/', 'max_age': 1.year})
90
+ services.cookie.set(cookie_key_name, { 'value': locale, 'path': '/', 'max_age': 1.year })
91
91
  end
92
92
 
93
93
  # The preview urls for all the sites share the same domain, so cookie[:locale]
@@ -25,7 +25,7 @@ module Locomotive::Steam
25
25
  if site.prefix_default_locale
26
26
  path_with_locale if locale_not_mentioned_in_path?
27
27
  else
28
- env['steam.path'] if default_locale? && locale_mentioned_in_path?
28
+ modify_path(env['steam.path']) if default_locale? && locale_mentioned_in_path?
29
29
  end
30
30
  end
31
31
  end
@@ -28,6 +28,7 @@ module Locomotive::Steam
28
28
  when 200 then '200 OK'
29
29
  when 301 then '301 Found'
30
30
  when 302 then '302 Found'
31
+ when 304 then '304 Not Modified'
31
32
  when 404 then '404 Not Found'
32
33
  when 422 then '422 Unprocessable Entity'
33
34
  end
@@ -0,0 +1,37 @@
1
+ module Locomotive::Steam
2
+ module Middlewares
3
+
4
+ # When rendering the page, the developer can stop it at anytime by
5
+ # raising an PageNotFoundException exception.
6
+ # Instead of the page, the 404 not found page will be rendered.
7
+ #
8
+ # This is particularly helpful with the dynamic routing feature
9
+ # to avoid duplicated page content (different urls, same HTTP 200 code but same blank page).
10
+ #
11
+ class PageNotFound < ThreadSafe
12
+
13
+ include Concerns::Helpers
14
+ include Concerns::Rendering
15
+
16
+ def _call
17
+ begin
18
+ self.next
19
+ rescue Locomotive::Steam::PageNotFoundException => e
20
+ # fetch the 404 error page...
21
+ env['steam.page'] = page_finder.find('404')
22
+
23
+ # ... and render it instead
24
+ render_page
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def page_finder
31
+ services.page_finder
32
+ end
33
+
34
+ end
35
+ end
36
+
37
+ end
@@ -14,7 +14,7 @@ module Locomotive::Steam
14
14
  begin
15
15
  self.next
16
16
  rescue Locomotive::Steam::RedirectionException => e
17
- redirect_to e.url, 302
17
+ redirect_to e.url, e.permanent ? 301 : 302
18
18
  end
19
19
  end
20
20
 
@@ -4,11 +4,11 @@ module Locomotive::Steam
4
4
  class Renderer < ThreadSafe
5
5
 
6
6
  include Concerns::Helpers
7
- include Concerns::LiquidContext
7
+ include Concerns::Rendering
8
8
 
9
9
  def _call
10
10
  if page
11
- render_page
11
+ render_page_or_redirect
12
12
  else
13
13
  render_missing_404
14
14
  end
@@ -16,33 +16,11 @@ module Locomotive::Steam
16
16
 
17
17
  private
18
18
 
19
- def render_page
19
+ def render_page_or_redirect
20
20
  if page.redirect?
21
21
  redirect_to(page.redirect_url, page.redirect_type)
22
22
  else
23
- content = parse_and_render_liquid
24
- render_response(content, page.not_found? ? 404 : 200, page.response_type)
25
- end
26
- end
27
-
28
- def render_missing_404
29
- message = (if locale != default_locale
30
- "Your 404 page is missing in the #{locale} locale."
31
- else
32
- "Your 404 page is missing."
33
- end) + " Please create it."
34
-
35
- log "[Warning] #{message}".red
36
- render_response(message, 404)
37
- end
38
-
39
- def parse_and_render_liquid
40
- document = services.liquid_parser.parse(page)
41
- begin
42
- document.render(liquid_context)
43
- rescue Locomotive::Steam::ParsingRenderingError => e
44
- e.file = page.template_path if e.file.blank?
45
- raise e
23
+ render_page
46
24
  end
47
25
  end
48
26
 
@@ -9,11 +9,7 @@ module Locomotive::Steam::Middlewares
9
9
  threadsafed = dup
10
10
  threadsafed.env = env
11
11
 
12
- # time = Benchmark.realtime do
13
12
  threadsafed._call # thread-safe purpose
14
- # end
15
-
16
- # puts "[Benchmark][#{self.class.name}] Time elapsed #{time*1000} milliseconds"
17
13
 
18
14
  threadsafed.next
19
15
  end
@@ -10,6 +10,7 @@ module Locomotive::Steam
10
10
  def initialize(source, page, per_page)
11
11
  @current_page, @per_page = page || 1, per_page || DEFAULT_PER_PAGE
12
12
 
13
+ @current_page = 1 if @current_page < 1
13
14
  @total_entries = source.count
14
15
  @total_pages = (@total_entries.to_f / @per_page).ceil
15
16
 
@@ -63,11 +63,13 @@ module Locomotive::Steam
63
63
  Middlewares::Locale,
64
64
  Middlewares::LocaleRedirection,
65
65
  Middlewares::Redirection,
66
- Middlewares::ImpersonatedEntry,
66
+ Middlewares::PageNotFound,
67
67
  Middlewares::Auth,
68
+ Middlewares::ImpersonatedEntry,
68
69
  Middlewares::PrivateAccess,
69
70
  Middlewares::Path,
70
71
  Middlewares::Page,
72
+ Middlewares::Cache,
71
73
  Middlewares::Section,
72
74
  Middlewares::Sitemap,
73
75
  Middlewares::TemplatizedPage
@@ -14,6 +14,7 @@ module Locomotive
14
14
  SERVICES = %w(content_entry api redirection cookie)
15
15
 
16
16
  BUILT_IN_FUNCTIONS = %w(
17
+ log
17
18
  getProp
18
19
  setProp
19
20
  getSessionProp
@@ -63,6 +64,10 @@ module Locomotive
63
64
  end
64
65
  end
65
66
 
67
+ def log_lambda(liquid_context)
68
+ -> (message) { Locomotive::Common::Logger.info(message) }
69
+ end
70
+
66
71
  def send_email_lambda(liquid_context)
67
72
  -> (options) { !!email.send_email(options, liquid_context) }
68
73
  end