locomotive_cms 2.5.4 → 2.5.5

Sign up to get free protection for your applications and to get access to all the features.
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