locomotive_cms 2.5.4 → 2.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/images/locomotive/icons/flags/bf.png +0 -0
  3. data/app/assets/images/locomotive/icons/flags/mg.png +0 -0
  4. data/app/assets/javascripts/locomotive/views/sites/memberships_view.js.coffee +4 -0
  5. data/app/controllers/locomotive/api/content_entries_controller.rb +1 -1
  6. data/app/controllers/locomotive/content_entries_controller.rb +1 -1
  7. data/app/controllers/locomotive/public/sitemaps_controller.rb +9 -0
  8. data/app/helpers/locomotive/base_helper.rb +13 -0
  9. data/app/helpers/locomotive/content_types_helper.rb +2 -1
  10. data/app/models/locomotive/content_entry.rb +46 -10
  11. data/app/models/locomotive/content_type.rb +3 -0
  12. data/app/models/locomotive/extensions/content_entry/csv.rb +2 -2
  13. data/app/models/locomotive/theme_asset.rb +2 -1
  14. data/app/views/locomotive/content_entries/_list.html.haml +1 -1
  15. data/app/views/locomotive/public/sitemaps/show.xml.builder +6 -4
  16. data/config/locales/admin_ui.es.yml +7 -0
  17. data/features/public/content_entries.feature +1 -1
  18. data/lib/generators/locomotive/install/templates/locomotive.rb +4 -0
  19. data/lib/locomotive/configuration.rb +2 -1
  20. data/lib/locomotive/liquid/asset_host.rb +51 -0
  21. data/lib/locomotive/liquid/drops/uploader.rb +7 -1
  22. data/lib/locomotive/liquid/filters/base.rb +8 -4
  23. data/lib/locomotive/liquid/tags/editable/file.rb +9 -5
  24. data/lib/locomotive/mongoid/patches.rb +44 -0
  25. data/lib/locomotive/render.rb +2 -1
  26. data/lib/locomotive/version.rb +1 -1
  27. data/spec/lib/locomotive/liquid/asset_host_spec.rb +79 -0
  28. data/spec/lib/locomotive/liquid/drops/content_entry_spec.rb +55 -19
  29. data/spec/lib/locomotive/liquid/filters/html_spec.rb +3 -1
  30. data/spec/lib/locomotive/liquid/tags/editable/file_spec.rb +72 -0
  31. data/spec/models/locomotive/content_type_spec.rb +31 -0
  32. data/spec/models/locomotive/theme_asset_spec.rb +4 -4
  33. data/spec/support/asset_host_stubs.rb +17 -0
  34. metadata +11 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ac131be58bf45b6af0f0ed0671fd79d88ea8fc7d
4
- data.tar.gz: 5763ccd2ca60d999473562a462cf74e44ef5baf2
3
+ metadata.gz: 934e34a25d00b8c0c55c81d20e17346b7a75f105
4
+ data.tar.gz: dd086d5f1ef9307fd1b6f4deb59c0581840b45c7
5
5
  SHA512:
6
- metadata.gz: 61a5b8f297196a3f412224f516938a741fc6145b24a92001ec9f781caf5d3897018ec6b07c2aa31cfc3bed6d7c30b94af160717157860ca9c0f0fec90c951baf
7
- data.tar.gz: 0c7da809d610b648f04c5dfb7386f687cb80d1a2087558e08553682de8969f2ca91e6b8630240b535494d1b3a8cd0be81663d4e2c5e647167384543295d8f1f6
6
+ metadata.gz: 3dd291b6d7e877f9187ec006a9d66300b176d7cecba047cf94a842f9d29f72d614f13e67a41006f0a1ac97537eac170134f8d31f468b99a1c2a3ca00b47c1c7a
7
+ data.tar.gz: 249b2e6f9b65fc55e13ff4aa31e40853d7f163a8c9e846fedeba3619ca456dd81e6610f5858b719fd8f8966f0e558e924772ab985bb5e840056120fe006d7df7
@@ -4,6 +4,8 @@ class Locomotive.Views.Sites.MembershipsView extends Backbone.View
4
4
 
5
5
  tagName: 'div'
6
6
 
7
+ id: 'site_memberships'
8
+
7
9
  className: 'list'
8
10
 
9
11
  _entry_views = []
@@ -13,6 +15,8 @@ class Locomotive.Views.Sites.MembershipsView extends Backbone.View
13
15
 
14
16
  @enable_ui_effects()
15
17
 
18
+ $(@el).append('<span class="error-anchor"></span>')
19
+
16
20
  return @
17
21
 
18
22
  change_entry: (membership, value) ->
@@ -38,7 +38,7 @@ module Locomotive
38
38
  protected
39
39
 
40
40
  def get_content_type
41
- @content_type ||= current_site.content_types.where(slug: params[:slug]).first
41
+ @content_type ||= current_site.content_types.by_id_or_slug(params[:slug]).first
42
42
  end
43
43
 
44
44
  end
@@ -77,7 +77,7 @@ module Locomotive
77
77
  protected
78
78
 
79
79
  def set_content_type
80
- @content_type ||= current_site.content_types.where(slug: params[:slug]).first
80
+ @content_type ||= current_site.content_types.by_id_or_slug(params[:slug]).first
81
81
  end
82
82
 
83
83
  def authorize_content
@@ -2,6 +2,8 @@ module Locomotive
2
2
  module Public
3
3
  class SitemapsController < Public::BaseController
4
4
 
5
+ before_filter :set_locale
6
+
5
7
  respond_to :xml
6
8
 
7
9
  def show
@@ -9,6 +11,13 @@ module Locomotive
9
11
  respond_with @pages
10
12
  end
11
13
 
14
+ protected
15
+
16
+ def set_locale
17
+ ::Mongoid::Fields::I18n.locale = params[:locale] || current_site.default_locale
18
+ ::I18n.locale = ::Mongoid::Fields::I18n.locale
19
+ end
20
+
12
21
  end
13
22
  end
14
23
  end
@@ -200,5 +200,18 @@ module Locomotive
200
200
  end
201
201
  alias :l :localize
202
202
 
203
+ # other helpers
204
+
205
+ # MongoDB crashes when performing a query on a big collection
206
+ # where there is a sort without an index on the fields to sort.
207
+ def empty_collection?(collection)
208
+ # criteria ?
209
+ if collection.respond_to?(:without_sorting)
210
+ collection.without_sorting.empty?
211
+ else
212
+ collection.empty?
213
+ end
214
+ end
215
+
203
216
  end
204
217
  end
@@ -61,7 +61,8 @@ module Locomotive
61
61
  registers = {
62
62
  controller: self,
63
63
  site: current_site,
64
- current_locomotive_account: current_locomotive_account
64
+ current_locomotive_account: current_locomotive_account,
65
+ asset_host: Locomotive::Liquid::AssetHost.new(request, current_site, Locomotive.config.asset_host)
65
66
  }
66
67
 
67
68
  preserve(content_type.item_template.render(::Liquid::Context.new({}, assigns, registers)))
@@ -36,6 +36,19 @@ module Locomotive
36
36
  scope :latest_updated, order_by(updated_at: :desc).limit(Locomotive.config.ui[:latest_entries_nb])
37
37
  scope :next_or_previous, ->(condition, order_by) { where({ _visible: true }.merge(condition)).limit(1).order_by(order_by) }
38
38
 
39
+ ## indexes ##
40
+ index site_id: 1
41
+ index _type: 1
42
+ index content_type_id: 1
43
+ Locomotive.config.site_locales.each do |locale|
44
+ index _type: 1, "_slug.#{locale}" => 1
45
+ index content_type_id: 1, "_slug.#{locale}" => 1
46
+ end
47
+ index content_type_id: 1, created_at: 1
48
+ index content_type_id: 1, _type: 1, created_at: 1
49
+ index _type: 1, _position: 1
50
+ index content_type_id: 1, _position: 1
51
+
39
52
  ## methods ##
40
53
 
41
54
  alias :visible? :_visible?
@@ -153,7 +166,7 @@ module Locomotive
153
166
  if self._slug.present?
154
167
  self._slug.permalink!
155
168
 
156
- self._slug = self.next_unique_slug if self.slug_already_taken?
169
+ self.find_next_unique_slug if self.slug_already_taken?
157
170
  end
158
171
 
159
172
  # all the site locales share the same slug ONLY IF the entry is not localized.
@@ -174,12 +187,36 @@ module Locomotive
174
187
  end
175
188
  end
176
189
 
177
- # Return the next available unique slug as a string
178
- def next_unique_slug
179
- slug = self._slug.gsub(/-\d+$/, '')
180
- similar_slugs = self.class.where(:_id.ne => self._id, _slug: /^#{slug}-?\d*$/i)
181
- next_number = similar_slugs.map {|s| s._slug.scan(/-(\d+)$/).flatten.last.to_i }.max + 1
182
- [slug, next_number].join('-')
190
+ # Find the next available unique slug as a string
191
+ # and replace the current _slug.
192
+ def find_next_unique_slug
193
+ _index = 0
194
+ _base = self._slug.gsub(/-\d+$/, '')
195
+
196
+ if _similar = similar_slug(_base)
197
+ _index = _similar.scan(/-(\d+)$/).flatten.last.to_i
198
+ end
199
+
200
+ loop do
201
+ _index += 1
202
+ self._slug = [_base, _index].join('-')
203
+ break unless self.slug_already_taken?
204
+ end
205
+ end
206
+
207
+ def similar_slug(slug)
208
+ _last = self.class.where(:_id.ne => self._id, _slug: /^#{slug}-?\d*$/i)
209
+ .only(:_slug)
210
+ .order_by(:_id.desc)
211
+ .context
212
+ .query
213
+ .first
214
+
215
+ if _last
216
+ _last['_slug'][::Mongoid::Fields::I18n.locale.to_s]
217
+ else
218
+ nil
219
+ end
183
220
  end
184
221
 
185
222
  def slug_already_taken?
@@ -203,9 +240,8 @@ module Locomotive
203
240
  end
204
241
 
205
242
  def add_to_list_bottom
206
- unless self.class.empty?
207
- self._position = self.class.max(:_position).to_i + 1
208
- end
243
+ max = self.class.indexed_max(:_position)
244
+ self._position = max + 1 if max
209
245
  end
210
246
 
211
247
  def send_notifications
@@ -40,6 +40,9 @@ module Locomotive
40
40
 
41
41
  ## named scopes ##
42
42
  scope :ordered, order_by(updated_at: :desc)
43
+ scope :by_id_or_slug, ->(id_or_slug) {
44
+ any_of({ _id: id_or_slug }, { slug: id_or_slug })
45
+ }
43
46
 
44
47
  ## indexes ##
45
48
  index site_id: 1, slug: 1
@@ -37,7 +37,7 @@ module Locomotive
37
37
  when :file
38
38
  value.blank? ? '' : value.guess_url(options[:host])
39
39
  when :belongs_to
40
- value.try(:_label)
40
+ value.try(:_label) || ''
41
41
  when :has_many, :many_to_many
42
42
  value.map(&:_label).join(', ')
43
43
  when :tags
@@ -68,7 +68,7 @@ module Locomotive
68
68
  # header
69
69
  csv << labels
70
70
  # body
71
- all.each do |entry|
71
+ all.each_by(100) do |entry|
72
72
  csv << entry.to_values(options)
73
73
  end
74
74
  end
@@ -155,7 +155,8 @@ module Locomotive
155
155
  sanitized_path = path.gsub(/[("')]/, '').gsub(/^\//, '').gsub(/\?[0-9]+$/, '')
156
156
 
157
157
  if asset = self.site.theme_assets.where(local_path: sanitized_path).first
158
- "#{path.first}#{asset.source.url}#{path.last}"
158
+ timestamp = self.updated_at.to_i
159
+ "#{path.first}#{asset.source.url}?#{timestamp}#{path.last}"
159
160
  else
160
161
  path
161
162
  end
@@ -1,4 +1,4 @@
1
- - if entries.empty?
1
+ - if empty_collection?(entries)
2
2
  %p.no-items!= t('.no_items', url: new_content_entry_path(content_type.slug))
3
3
  - else
4
4
  %ul{ id: 'entries-list', class: "#{content_type.groupable? ? 'grouped' : 'list'} #{'sortable' if content_type.order_manually?}", :'data-url' => sort_content_entries_path(content_type.slug, :json) }
@@ -10,10 +10,12 @@ xml.urlset "xmlns" => "http://www.sitemaps.org/schemas/sitemap/0.9" do
10
10
  if not page.index_or_not_found?
11
11
  if page.templatized?
12
12
  page.fetch_target_entries(_visible: true).each do |c|
13
- xml.url do
14
- xml.loc public_page_url(page, { content: c })
15
- xml.lastmod c.updated_at.to_date.to_s('%Y-%m-%d')
16
- xml.priority 0.9
13
+ if c._slug.present?
14
+ xml.url do
15
+ xml.loc public_page_url(page, { content: c })
16
+ xml.lastmod c.updated_at.to_date.to_s('%Y-%m-%d')
17
+ xml.priority 0.9
18
+ end
17
19
  end
18
20
  end
19
21
  else
@@ -60,11 +60,16 @@ es:
60
60
  disable_with: "locomotive.disable_with.form_actions"
61
61
  footer:
62
62
  who_is_behind: "Servicio desarrollado por %{development} y diseñado por <a href=\"http://www.sachagreif.com\">Sacha Greif</a>"
63
+ form:
64
+ change_file: cambiar
65
+ delete_file: eliminar
66
+ cancel: cancelar
63
67
  form_actions:
64
68
  back: Volver sin guardar
65
69
  create: Crear
66
70
  update: Actualizar
67
71
  send: Enviar
72
+ disable_with: Pendiente...
68
73
 
69
74
  notifications:
70
75
  new_content_entry:
@@ -86,6 +91,8 @@ es:
86
91
  index:
87
92
  is_required: requerido
88
93
  default_label: Nombre del campo
94
+ form:
95
+ default_label: Nombre del campo
89
96
 
90
97
  sessions:
91
98
  new:
@@ -54,7 +54,7 @@ Scenario: Filter by a date
54
54
  """
55
55
  Lorem ipsum, Yadi Yada
56
56
  """
57
-
57
+
58
58
  Scenario: Filter with regexp
59
59
  Given a page named "my-articles" with the template:
60
60
  """
@@ -65,6 +65,10 @@ Locomotive.configure do |config|
65
65
  #
66
66
  # config.theme_assets_checksum = true
67
67
 
68
+ # Enable serving of images, stylesheets, and JavaScripts from an asset server
69
+ # config.asset_host = 'http://assets.example.com'
70
+ # config.asset_host = -> (request, site) { ... }
71
+
68
72
  # Rack-cache settings, mainly used for the inline resizing image module. Default options:
69
73
  # config.rack_cache = {
70
74
  # verbose: true,
@@ -31,7 +31,8 @@ module Locomotive
31
31
  context_assign_extensions: { },
32
32
  models_for_templatization: [],
33
33
  csrf_protection: false,
34
- theme_assets_checksum: false
34
+ theme_assets_checksum: false,
35
+ asset_host: nil
35
36
  }
36
37
 
37
38
  cattr_accessor :settings
@@ -0,0 +1,51 @@
1
+ module Locomotive
2
+ module Liquid
3
+
4
+ class AssetHost
5
+
6
+ IsHTTP = /^https?\/\//o
7
+
8
+ attr_reader :request, :site, :host
9
+
10
+ def initialize(request, site, host)
11
+ @request, @site = request, site
12
+
13
+ @host = build_host(host, request, site)
14
+ end
15
+
16
+ def compute(source, timestamp = nil)
17
+ return source if source.nil?
18
+
19
+ return add_timestamp_suffix(source, timestamp) if source =~ IsHTTP
20
+
21
+ url = self.host ? URI.join(host, source).to_s : source
22
+
23
+ add_timestamp_suffix(url, timestamp)
24
+ end
25
+
26
+ private
27
+
28
+ def build_host(host, request, site)
29
+ if host
30
+ if host.respond_to?(:call)
31
+ host.call(request, site)
32
+ else
33
+ host
34
+ end
35
+ else
36
+ nil
37
+ end
38
+ end
39
+
40
+ def add_timestamp_suffix(source, timestamp)
41
+ if timestamp.nil? || timestamp == 0 || source.include?('?')
42
+ source
43
+ else
44
+ "#{source}?#{timestamp}"
45
+ end
46
+ end
47
+
48
+ end
49
+
50
+ end
51
+ end
@@ -3,7 +3,13 @@ module Locomotive
3
3
  module Drops
4
4
  class Uploader < Base
5
5
 
6
- delegate :url, :size, to: :@_source
6
+ delegate :size, to: :@_source
7
+
8
+ def url
9
+ url, timestamp = @_source.url, @_source.model.updated_at.to_i
10
+
11
+ @context.registers[:asset_host].compute(url, timestamp)
12
+ end
7
13
 
8
14
  def filename
9
15
  File.basename(@_source.url)
@@ -35,11 +35,15 @@ module Locomotive
35
35
  path.gsub!(/(\?+.+)$/, '')
36
36
  query_string = $1
37
37
 
38
- url = ThemeAssetUploader.url_for(@context.registers[:site], path)
38
+ # build the url of the theme asset based on the site and without loading
39
+ # the whole theme asset from database
40
+ _url = ThemeAssetUploader.url_for(@context.registers[:site], path)
39
41
 
40
- if checksum = @context.registers[:theme_assets_checksum][path]
41
- query_string = "?#{checksum}" if query_string.blank?
42
- end
42
+ # get a timestamp only the source url does not include a query string
43
+ timestamp = query_string.blank? ? @context.registers[:theme_assets_checksum][path] : nil
44
+
45
+ # prefix by a asset host if given
46
+ url = @context.registers[:asset_host].compute(_url, timestamp)
43
47
 
44
48
  query_string ? "#{url}#{query_string}" : url
45
49
  end
@@ -15,15 +15,19 @@ module Locomotive
15
15
  end
16
16
 
17
17
  def render_element(context, element)
18
- if element.source?
19
- element.source.url
18
+ default_timestamp = context.registers[:page].updated_at.to_i
19
+
20
+ url, timestamp = (if element.source?
21
+ [element.source.url, default_timestamp]
20
22
  else
21
23
  if element.default_source_url.present?
22
- element.default_source_url
24
+ [element.default_source_url, default_timestamp]
23
25
  else
24
- render_default_content(context)
26
+ [render_default_content(context), nil]
25
27
  end
26
- end
28
+ end)
29
+
30
+ context.registers[:asset_host].compute(url, timestamp)
27
31
  end
28
32
 
29
33
  def document_type
@@ -22,9 +22,53 @@ module Mongoid#:nodoc:
22
22
  end
23
23
 
24
24
  class Criteria
25
+ def without_sorting
26
+ clone.tap { |crit| crit.options.delete(:sort) }
27
+ end
28
+
29
+ # http://code.dblock.org/paging-and-iterating-over-large-mongo-collections
30
+ def each_by(by, &block)
31
+ idx = total = 0
32
+ set_limit = options[:limit]
33
+ while ((results = ordered_clone.limit(by).skip(idx)) && results.any?)
34
+ results.each do |result|
35
+ return self if set_limit and set_limit >= total
36
+ total += 1
37
+ yield result
38
+ end
39
+ idx += by
40
+ end
41
+ self
42
+ end
43
+
44
+ # Optimized version of the max aggregate method.
45
+ # It works efficiently only if the field is part of a MongoDB index.
46
+ # more here: http://stackoverflow.com/questions/4762980/getting-the-highest-value-of-a-column-in-mongodb
47
+ def indexed_max(field)
48
+ _criteria = criteria.order_by(field.to_sym.desc).only(field.to_sym)
49
+ selector = _criteria.send(:selector_with_type_selection)
50
+ fields = _criteria.options[:fields]
51
+ sort = _criteria.options[:sort]
52
+
53
+ document = collection.find(selector).select(fields).sort(sort).limit(1).first
54
+ document ? document[field.to_s].to_i : nil
55
+ end
56
+
25
57
  def to_liquid
26
58
  Locomotive::Liquid::Drops::ProxyCollection.new(self)
27
59
  end
60
+
61
+ private
62
+
63
+ def ordered_clone
64
+ options[:sort] ? clone : clone.asc(:_id)
65
+ end
66
+ end
67
+
68
+ module Finders
69
+ def indexed_max(field)
70
+ with_default_scope.indexed_max(field)
71
+ end
28
72
  end
29
73
 
30
74
  module Criterion
@@ -222,7 +222,8 @@ module Locomotive
222
222
  inline_editor: self.editing_page?,
223
223
  logger: Rails.logger,
224
224
  current_locomotive_account: current_locomotive_account,
225
- theme_assets_checksum: Locomotive.config.theme_assets_checksum ? current_site.theme_assets.checksums : {}
225
+ theme_assets_checksum: Locomotive.config.theme_assets_checksum ? current_site.theme_assets.checksums : {},
226
+ asset_host: Locomotive::Liquid::AssetHost.new(request, current_site, Locomotive.config.asset_host)
226
227
  }
227
228
  end
228
229
 
@@ -1,3 +1,3 @@
1
1
  module Locomotive #:nodoc
2
- VERSION = '2.5.4'
2
+ VERSION = '2.5.5'
3
3
  end
@@ -0,0 +1,79 @@
1
+ require 'spec_helper'
2
+
3
+ describe Locomotive::Liquid::AssetHost do
4
+
5
+ let(:request) { nil }
6
+ let(:site) { nil }
7
+ let(:host) { nil }
8
+ let(:timestamp) { nil }
9
+ let(:asset_host) { Locomotive::Liquid::AssetHost.new(request, site, host) }
10
+ let(:source) { '/sites/42/assets/1/banner.png' }
11
+
12
+ subject { asset_host.compute(source, timestamp) }
13
+
14
+ describe 'no host provided' do
15
+
16
+ it { should eq '/sites/42/assets/1/banner.png' }
17
+
18
+ end
19
+
20
+ describe 'with a timestamp' do
21
+
22
+ let(:timestamp) { '42' }
23
+ it { should eq '/sites/42/assets/1/banner.png?42' }
24
+
25
+ context 'the source already includes a query string' do
26
+
27
+ let(:source) { '/sites/42/assets/1/banner.png?foo' }
28
+ it { should eq '/sites/42/assets/1/banner.png?foo' }
29
+
30
+ end
31
+
32
+ end
33
+
34
+ describe 'the source is already a full url' do
35
+
36
+ let(:source) { 'http://somewhere.net/sites/42/assets/1/banner.png' }
37
+ it { should eq 'http://somewhere.net/sites/42/assets/1/banner.png' }
38
+
39
+ describe 'also with https' do
40
+
41
+ let(:source) { 'https://somewhere.net/sites/42/assets/1/banner.png' }
42
+ it { should eq 'https://somewhere.net/sites/42/assets/1/banner.png' }
43
+
44
+ end
45
+
46
+ end
47
+
48
+ describe 'the host is a string' do
49
+
50
+ let(:host) { 'http://assets.locomotivecms.com' }
51
+ it { should eq 'http://assets.locomotivecms.com/sites/42/assets/1/banner.png' }
52
+
53
+ end
54
+
55
+ describe 'the host is a block' do
56
+
57
+ let(:request) { stub(ssl: true) }
58
+ let(:site) { stub(cdn: true) }
59
+ let(:host) { ->(request, site) { site.cdn ? "http#{request.ssl ? 's' : ''}://assets.locomotivecms.com" : nil } }
60
+
61
+ it { should eq 'https://assets.locomotivecms.com/sites/42/assets/1/banner.png' }
62
+
63
+ context 'with a different request var' do
64
+
65
+ let(:request) { stub(ssl: false) }
66
+ it { should eq 'http://assets.locomotivecms.com/sites/42/assets/1/banner.png' }
67
+
68
+ end
69
+
70
+ context 'with a different site var' do
71
+
72
+ let(:site) { stub(cdn: false) }
73
+ it { should eq '/sites/42/assets/1/banner.png' }
74
+
75
+ end
76
+
77
+ end
78
+
79
+ end
@@ -2,45 +2,81 @@ require 'spec_helper'
2
2
 
3
3
  describe Locomotive::Liquid::Drops::ContentEntry do
4
4
 
5
- before(:each) do
6
- @list = mock('list')
7
- @list.stubs(:all).returns(true)
8
- @category = Locomotive::Liquid::Drops::ContentEntry.new(mock('category', projects: @list))
9
- end
5
+ describe 'with a file' do
6
+
7
+ before { Locomotive::Site.any_instance.stubs(:create_default_pages!).returns(true) }
10
8
 
11
- context '#accessing a has_many relationship' do
9
+ let(:content_type) { build_content_type }
10
+ let(:content_entry) { content_type.entries.build(title: 'Locomotive', description: 'Lorem ipsum....', _label_field_name: 'title', created_at: Time.zone.parse('2013-07-05 00:00:00'), file: FixturedAsset.open('5k.png'), updated_at: DateTime.parse('2007-06-29 21:00:00')) }
11
+ let(:content_entry_drop) { Locomotive::Liquid::Drops::ContentEntry.new(content_entry) }
12
12
 
13
- it 'loops through the list' do
14
- template = %({% for project in category.projects %}{{ project }},{% endfor %})
13
+ describe 'displaying the timestamp' do
15
14
 
16
- @list.expects(:ordered).returns(%w(a b))
15
+ subject { render('{{ article.file.url }}', { 'article' => content_entry_drop }) }
16
+
17
+ it { should include '5k.png?1183150800' }
17
18
 
18
- render(template, { 'category' => @category }).should == 'a,b,'
19
19
  end
20
20
 
21
- it 'filters the list' do
22
- template = %({% with_scope order_by: 'name ASC', active: true %}{% for project in category.projects %}{{ project }},{% endfor %}{% endwith_scope %})
21
+ end
23
22
 
24
- @list.expects(:filtered).with({ 'active' => true }, ['name', 'ASC']).returns(%w(a b))
23
+ describe 'a list of entries' do
25
24
 
26
- render(template, { 'category' => @category }).should == 'a,b,'
25
+ before(:each) do
26
+ @list = mock('list')
27
+ @list.stubs(:all).returns(true)
28
+ @category = Locomotive::Liquid::Drops::ContentEntry.new(mock('category', projects: @list))
27
29
  end
28
30
 
29
- it 'filters the list and uses the default order' do
30
- template = %({% with_scope active: true %}{% for project in category.projects %}{{ project }},{% endfor %}{% endwith_scope %})
31
+ context '#accessing a has_many relationship' do
32
+
33
+ it 'loops through the list' do
34
+ template = %({% for project in category.projects %}{{ project }},{% endfor %})
35
+
36
+ @list.expects(:ordered).returns(%w(a b))
37
+
38
+ render(template, { 'category' => @category }).should == 'a,b,'
39
+ end
40
+
41
+ it 'filters the list' do
42
+ template = %({% with_scope order_by: 'name ASC', active: true %}{% for project in category.projects %}{{ project }},{% endfor %}{% endwith_scope %})
31
43
 
32
- @list.expects(:filtered).with({ 'active' => true }, nil).returns(%w(a b))
44
+ @list.expects(:filtered).with({ 'active' => true }, ['name', 'ASC']).returns(%w(a b))
45
+
46
+ render(template, { 'category' => @category }).should == 'a,b,'
47
+ end
48
+
49
+ it 'filters the list and uses the default order' do
50
+ template = %({% with_scope active: true %}{% for project in category.projects %}{{ project }},{% endfor %}{% endwith_scope %})
51
+
52
+ @list.expects(:filtered).with({ 'active' => true }, nil).returns(%w(a b))
53
+
54
+ render(template, { 'category' => @category }).should == 'a,b,'
55
+ end
33
56
 
34
- render(template, { 'category' => @category }).should == 'a,b,'
35
57
  end
36
58
 
37
59
  end
38
60
 
39
61
  def render(template, assigns = {})
40
- liquid_context = ::Liquid::Context.new(assigns, {}, {})
62
+ liquid_context = ::Liquid::Context.new(assigns, {}, {
63
+ asset_host: TimestampAssetHost.new
64
+ })
41
65
 
42
66
  output = ::Liquid::Template.parse(template).render(liquid_context)
43
67
  output.gsub(/\n\s{0,}/, '')
44
68
  end
45
69
 
70
+ def build_content_type
71
+ FactoryGirl.build(:content_type).tap do |content_type|
72
+ content_type.entries_custom_fields.build label: 'Title', type: 'string'
73
+ content_type.entries_custom_fields.build label: 'Description', type: 'text'
74
+ content_type.entries_custom_fields.build label: 'Visible ?', type: 'boolean', name: 'visible'
75
+ content_type.entries_custom_fields.build label: 'File', type: 'file'
76
+ content_type.entries_custom_fields.build label: 'Published at', type: 'date'
77
+ content_type.valid?
78
+ content_type.send(:set_label_field)
79
+ end
80
+ end
81
+
46
82
  end
@@ -236,12 +236,14 @@ describe Locomotive::Liquid::Filters::Html do
236
236
  end
237
237
 
238
238
  def build_context
239
+
239
240
  klass = Class.new
240
241
  klass.class_eval do
241
242
  def registers
242
243
  @registers ||= {
243
244
  site: FactoryGirl.build(:site, id: fake_bson_id(42)),
244
- theme_assets_checksum: {}
245
+ theme_assets_checksum: {},
246
+ asset_host: TimestampAssetHost.new
245
247
  }
246
248
  end
247
249
 
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ describe Locomotive::Liquid::Tags::Editable::File do
4
+
5
+ before { Locomotive::Site.any_instance.stubs(:create_default_pages!).returns(true) }
6
+
7
+ let(:asset_host) { CdnAssetHost.new }
8
+ let(:page) { Locomotive::Page.new(updated_at: DateTime.parse('2007-06-29 21:00:00')) }
9
+
10
+ subject { render("{% editable_file banner %}http://www.placehold.it/500x500{% endeditable_file %}") }
11
+
12
+ describe 'no uploaded file' do
13
+
14
+ let(:asset_host) { IsoAssetHost.new }
15
+ before { add_editable_file({}) }
16
+ it { should eq 'http://www.placehold.it/500x500' }
17
+
18
+ context 'a timestamp is not applicable' do
19
+
20
+ let(:asset_host) { TimestampAssetHost.new }
21
+ it { should eq 'http://www.placehold.it/500x500' }
22
+
23
+ end
24
+
25
+ end
26
+
27
+ describe 'with a default source url' do
28
+
29
+ before { add_editable_file(default_source_url: '/assets/42/assets/1/foo.png') }
30
+ it { should eq 'http://cdn.locomotivecms.com/assets/42/assets/1/foo.png' }
31
+
32
+ context 'has a timestamp' do
33
+
34
+ let(:asset_host) { TimestampAssetHost.new }
35
+ it { should eq '/assets/42/assets/1/foo.png?1183150800' }
36
+
37
+ end
38
+
39
+ end
40
+
41
+ describe 'with an uploaded file' do
42
+
43
+ before { add_editable_file }
44
+ it { should match /^http:\/\/cdn\.locomotivecms\.com\/spec\/(.*)\/5k.png$/ }
45
+
46
+ context 'has a timestamp' do
47
+
48
+ let(:asset_host) { TimestampAssetHost.new }
49
+ it { should match /^\/spec\/(.*)\/5k.png\?1183150800$/ }
50
+
51
+ end
52
+
53
+ end
54
+
55
+ def render(template, assigns = {})
56
+ liquid_context = ::Liquid::Context.new(assigns, {}, {
57
+ asset_host: asset_host,
58
+ page: page
59
+ }, true)
60
+
61
+ output = ::Liquid::Template.parse(template).render(liquid_context)
62
+ output.gsub(/\n\s{0,}/, '')
63
+ end
64
+
65
+ def add_editable_file(attributes = nil)
66
+ attributes ||= { source: FixturedAsset.open('5k.png') }
67
+
68
+ editable_file = Locomotive::EditableFile.new({ slug: 'banner' }.merge(attributes))
69
+ page.editable_elements << editable_file
70
+ end
71
+
72
+ end
@@ -348,6 +348,37 @@ describe Locomotive::ContentType do
348
348
 
349
349
  end
350
350
 
351
+ describe 'finding by id or slug' do
352
+
353
+ let(:content_type) { build_content_type }
354
+ let(:id_or_slug) { 'unknown' }
355
+
356
+ subject { Locomotive::ContentType.by_id_or_slug(id_or_slug).first }
357
+
358
+ before { content_type.save }
359
+
360
+ describe 'unknown id' do
361
+
362
+ it { should eq nil }
363
+
364
+ end
365
+
366
+ describe 'existing id' do
367
+
368
+ let(:id_or_slug) { content_type._id.to_s }
369
+ its(:name) { should eq 'My project' }
370
+
371
+ end
372
+
373
+ describe 'existing slug' do
374
+
375
+ let(:id_or_slug) { 'my_project' }
376
+ its(:name) { should eq 'My project' }
377
+
378
+ end
379
+
380
+ end
381
+
351
382
  def build_content_type(options = {}, &block)
352
383
  FactoryGirl.build(:content_type, options).tap do |content_type|
353
384
  content_type.entries_custom_fields.build label: 'Name', type: 'string'
@@ -6,7 +6,7 @@ describe Locomotive::ThemeAsset do
6
6
 
7
7
  let(:site) { FactoryGirl.build(:site, domains: %w{www.acme.com}) }
8
8
 
9
- let(:asset) { FactoryGirl.build(:theme_asset, site: site) }
9
+ let(:asset) { FactoryGirl.build(:theme_asset, site: site, updated_at: DateTime.parse('2007/06/29 21:10:00')) }
10
10
 
11
11
  describe 'attaching a file' do
12
12
 
@@ -145,21 +145,21 @@ describe Locomotive::ThemeAsset do
145
145
  context 'simple url' do
146
146
 
147
147
  let(:text) { "background: url(/images/banner.png) no-repeat 0 0" }
148
- it { should == "background: url(http://engine.dev/images/banner.png) no-repeat 0 0" }
148
+ it { should == "background: url(http://engine.dev/images/banner.png?1183151400) no-repeat 0 0" }
149
149
 
150
150
  end
151
151
 
152
152
  context 'url with quotes' do
153
153
 
154
154
  let(:text) { "background: url(\"/images/banner.png\") no-repeat 0 0" }
155
- it { should == "background: url(\"http://engine.dev/images/banner.png\") no-repeat 0 0" }
155
+ it { should == "background: url(\"http://engine.dev/images/banner.png?1183151400\") no-repeat 0 0" }
156
156
 
157
157
  end
158
158
 
159
159
  context 'url with quotes and timestamps' do
160
160
 
161
161
  let(:text) { "background: url(\"/images/banner.png?123456\") no-repeat 0 0" }
162
- it { should == "background: url(\"http://engine.dev/images/banner.png\") no-repeat 0 0" }
162
+ it { should == "background: url(\"http://engine.dev/images/banner.png?1183151400\") no-repeat 0 0" }
163
163
 
164
164
  end
165
165
 
@@ -0,0 +1,17 @@
1
+ class IsoAssetHost
2
+ def compute(source, timestamp = nil)
3
+ source
4
+ end
5
+ end
6
+
7
+ class CdnAssetHost
8
+ def compute(source, timestamp = nil)
9
+ "http://cdn.locomotivecms.com#{source}"
10
+ end
11
+ end
12
+
13
+ class TimestampAssetHost
14
+ def compute(source, timestamp = nil)
15
+ timestamp ? "#{source}?#{timestamp}" : source
16
+ end
17
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: locomotive_cms
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.4
4
+ version: 2.5.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Didier Lafforgue
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-19 00:00:00.000000000 Z
11
+ date: 2014-08-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -545,6 +545,7 @@ files:
545
545
  - app/assets/images/locomotive/datepicker/ui-widget-content-top.png
546
546
  - app/assets/images/locomotive/form/error-arrow.png
547
547
  - app/assets/images/locomotive/form/input-sep.png
548
+ - app/assets/images/locomotive/icons/flags/bf.png
548
549
  - app/assets/images/locomotive/icons/flags/bg.png
549
550
  - app/assets/images/locomotive/icons/flags/cs.png
550
551
  - app/assets/images/locomotive/icons/flags/de.png
@@ -554,6 +555,7 @@ files:
554
555
  - app/assets/images/locomotive/icons/flags/fr.png
555
556
  - app/assets/images/locomotive/icons/flags/it.png
556
557
  - app/assets/images/locomotive/icons/flags/ja.png
558
+ - app/assets/images/locomotive/icons/flags/mg.png
557
559
  - app/assets/images/locomotive/icons/flags/nb.png
558
560
  - app/assets/images/locomotive/icons/flags/nl.png
559
561
  - app/assets/images/locomotive/icons/flags/pl.png
@@ -1172,6 +1174,7 @@ files:
1172
1174
  - lib/locomotive/httparty/webservice.rb
1173
1175
  - lib/locomotive/kaminari.rb
1174
1176
  - lib/locomotive/liquid.rb
1177
+ - lib/locomotive/liquid/asset_host.rb
1175
1178
  - lib/locomotive/liquid/drops/base.rb
1176
1179
  - lib/locomotive/liquid/drops/content_entry.rb
1177
1180
  - lib/locomotive/liquid/drops/content_types.rb
@@ -1299,6 +1302,7 @@ files:
1299
1302
  - spec/lib/core_ext_spec.rb
1300
1303
  - spec/lib/locomotive/configuration_spec.rb
1301
1304
  - spec/lib/locomotive/httparty/webservice_spec.rb
1305
+ - spec/lib/locomotive/liquid/asset_host_spec.rb
1302
1306
  - spec/lib/locomotive/liquid/drops/content_entry_spec.rb
1303
1307
  - spec/lib/locomotive/liquid/drops/current_user.rb
1304
1308
  - spec/lib/locomotive/liquid/drops/page_spec.rb
@@ -1311,6 +1315,7 @@ files:
1311
1315
  - spec/lib/locomotive/liquid/filters/translate_spec.rb
1312
1316
  - spec/lib/locomotive/liquid/tags/consume_spec.rb
1313
1317
  - spec/lib/locomotive/liquid/tags/csrf_spec.rb
1318
+ - spec/lib/locomotive/liquid/tags/editable/file_spec.rb
1314
1319
  - spec/lib/locomotive/liquid/tags/editable/text_spec.rb
1315
1320
  - spec/lib/locomotive/liquid/tags/extends_spec.rb
1316
1321
  - spec/lib/locomotive/liquid/tags/javascript_spec.rb
@@ -1346,6 +1351,7 @@ files:
1346
1351
  - spec/models/locomotive/theme_asset_spec.rb
1347
1352
  - spec/requests/admin_ssl_spec.rb
1348
1353
  - spec/requests/seo_trailing_slash_spec.rb
1354
+ - spec/support/asset_host_stubs.rb
1349
1355
  - spec/support/carrierwave.rb
1350
1356
  - spec/support/cells.rb
1351
1357
  - spec/support/controller.rb
@@ -1532,6 +1538,7 @@ test_files:
1532
1538
  - spec/lib/core_ext_spec.rb
1533
1539
  - spec/lib/locomotive/configuration_spec.rb
1534
1540
  - spec/lib/locomotive/httparty/webservice_spec.rb
1541
+ - spec/lib/locomotive/liquid/asset_host_spec.rb
1535
1542
  - spec/lib/locomotive/liquid/drops/content_entry_spec.rb
1536
1543
  - spec/lib/locomotive/liquid/drops/current_user.rb
1537
1544
  - spec/lib/locomotive/liquid/drops/page_spec.rb
@@ -1544,6 +1551,7 @@ test_files:
1544
1551
  - spec/lib/locomotive/liquid/filters/translate_spec.rb
1545
1552
  - spec/lib/locomotive/liquid/tags/consume_spec.rb
1546
1553
  - spec/lib/locomotive/liquid/tags/csrf_spec.rb
1554
+ - spec/lib/locomotive/liquid/tags/editable/file_spec.rb
1547
1555
  - spec/lib/locomotive/liquid/tags/editable/text_spec.rb
1548
1556
  - spec/lib/locomotive/liquid/tags/extends_spec.rb
1549
1557
  - spec/lib/locomotive/liquid/tags/javascript_spec.rb
@@ -1579,6 +1587,7 @@ test_files:
1579
1587
  - spec/models/locomotive/theme_asset_spec.rb
1580
1588
  - spec/requests/admin_ssl_spec.rb
1581
1589
  - spec/requests/seo_trailing_slash_spec.rb
1590
+ - spec/support/asset_host_stubs.rb
1582
1591
  - spec/support/carrierwave.rb
1583
1592
  - spec/support/cells.rb
1584
1593
  - spec/support/controller.rb