locomotive_cms 2.0.1 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -15,6 +15,8 @@ module Locomotive
15
15
 
16
16
  before_filter :set_current_thread_variables
17
17
 
18
+ rescue_from Exception, with: :render_access_denied
19
+
18
20
  self.responder = Locomotive::ActionController::Responder # custom responder
19
21
 
20
22
  respond_to :json, :xml
@@ -41,6 +43,15 @@ module Locomotive
41
43
  self.setup_i18n_fallbacks
42
44
  end
43
45
 
46
+ def render_access_denied(exception)
47
+ status = (case exception
48
+ when ::CanCan::AccessDenied then 401
49
+ when ::Mongoid::Errors::DocumentNotFound then 404
50
+ else 500
51
+ end)
52
+ render json: { error: exception.message }, status: status, layout: false
53
+ end
54
+
44
55
  def self.cancan_resource_class
45
56
  Locomotive::Api::CanCan::ControllerResource
46
57
  end
@@ -38,7 +38,7 @@ module CustomFields
38
38
 
39
39
  # Set correct paths
40
40
  def store_dir
41
- "sites/#{model.site_id}/content_#{model.class.model_name.demodulize.underscore}/#{model.id}/files"
41
+ "sites/#{model.site_id}/#{model.class.model_name.demodulize.underscore}/#{model.id}/files"
42
42
  end
43
43
 
44
44
  # In some situations, for instance, for the notification email when a content entry is created,
@@ -7,18 +7,39 @@ module Locomotive
7
7
  end
8
8
 
9
9
  def call(env)
10
- path = env['PATH_INFO']
10
+ path, query = env['PATH_INFO'], env['QUERY_STRING']
11
11
 
12
12
  if !path.starts_with?("#{Locomotive.mounted_on}/") && (match = path.match(%r{(.+)/$}))
13
- response = Rack::Response.new
14
- response.redirect(match[1], 301) # moved permanently
15
- response.finish
16
- response.to_a
13
+ url = self.redirect_url(match[1], query)
14
+ self.redirect_to(url)
17
15
  else
18
16
  @app.call(env)
19
17
  end
20
18
  end
21
19
 
20
+ protected
21
+
22
+ # Create a 301 response and set it up accordingly.
23
+ #
24
+ # @params [ String ] url The url for the redirection
25
+ #
26
+ # @return [ Array ] It has the 3 parameters (status, header, body)
27
+ #
28
+ def redirect_to(url)
29
+ response = Rack::Response.new
30
+ response.redirect(url, 301) # moved permanently
31
+ response.finish
32
+ response.to_a
33
+ end
34
+
35
+ def redirect_url(base, query)
36
+ if query.blank?
37
+ base
38
+ else
39
+ "#{base}?#{query}"
40
+ end
41
+ end
42
+
22
43
  end
23
44
  end
24
45
  end
@@ -1,3 +1,3 @@
1
1
  module Locomotive #:nodoc
2
- VERSION = '2.0.1'
2
+ VERSION = '2.0.2'
3
3
  end
@@ -0,0 +1,107 @@
1
+ class MakeEditableElementsConsistent < MongoidMigration::Migration
2
+ def self.up
3
+ Locomotive::Site.all.each do |site|
4
+ site.locales.each do |locale|
5
+ ::Mongoid::Fields::I18n.locale = locale
6
+ site.pages.each do |page|
7
+ puts "[#{site.name}] #{page.fullpath} (#{locale})"
8
+
9
+ found_elements = []
10
+
11
+ page.template.walk do |node, memo|
12
+ case node
13
+ when Locomotive::Liquid::Tags::InheritedBlock
14
+ puts "found block ! #{node.name} --- #{memo[:parent_block_name]}"
15
+
16
+ # set the new name based on a potential parent block
17
+ name = node.name.gsub(/[\"\']/o, '')
18
+
19
+ if memo[:parent_block_name] && !name.starts_with?(memo[:parent_block_name])
20
+ name = "#{memo[:parent_block_name]}/#{name}"
21
+ end
22
+
23
+ puts "new_name = #{name}"
24
+
25
+ # retrieve all the editable elements of this block and set them the new name
26
+ page.find_editable_elements(node.name).each do |el|
27
+ # puts "**> hurray found the element #{el.block} _ #{el.slug}"
28
+ el.block = name
29
+ puts "**> hurray found the element #{el.block} _ #{el.slug} | #{page.find_editable_element(name, el.slug).present?.inspect}"
30
+ end
31
+
32
+ # assign the new name to the block
33
+ node.instance_variable_set :@name, name
34
+
35
+ # record the new parent block name for children
36
+ memo[:parent_block_name] = name
37
+
38
+ when Locomotive::Liquid::Tags::Editable::ShortText,
39
+ Locomotive::Liquid::Tags::Editable::LongText,
40
+ Locomotive::Liquid::Tags::Editable::Control,
41
+ Locomotive::Liquid::Tags::Editable::File
42
+
43
+ puts "\tfound editable_element ! #{node.slug} --- #{memo[:parent_block_name]}"
44
+
45
+ slug = node.slug.gsub(/[\"\']/o, '')
46
+
47
+ # assign the new slug to the editable element
48
+ puts "\t\t...looking for #{node.slug} inside #{memo[:parent_block_name]}"
49
+
50
+ options = node.instance_variable_get :@options
51
+ block = options[:block].blank? ? memo[:parent_block_name] : options[:block]
52
+
53
+ if el = page.find_editable_element(block, node.slug)
54
+ puts "\t\t--> yep found the element"
55
+
56
+ el.slug = slug
57
+ el.block = memo[:parent_block_name] # just to make sure
58
+
59
+ node.instance_variable_set :@slug, slug
60
+
61
+ options.delete(:block)
62
+ node.instance_variable_set :@block, nil # just to make sure
63
+
64
+ found_elements << el._id
65
+ else
66
+ puts "\t\t[WARNING] el not found (#{block} - #{slug})"
67
+ end
68
+
69
+ end
70
+
71
+ memo
72
+ end # page walk
73
+
74
+ puts "found elements = #{found_elements.join(', ')} / #{page.editable_elements.count}"
75
+
76
+ # "hide" useless editable elements
77
+ page.editable_elements.each do |el|
78
+ next if found_elements.include?(el._id)
79
+ el.disabled = true
80
+ end
81
+
82
+ # serialize
83
+ page.send(:_serialize_template)
84
+
85
+ # puts page.template.inspect
86
+
87
+ # save ?
88
+ page.instance_variable_set :@template_changed, false
89
+ page.save
90
+
91
+ # TODO:
92
+ # x ", block: 'Asset #1'"" ???? les re-assigner a "main" d'une facon ou d'une autre
93
+ # => en fait, ce sont des editable elements qui n'ont pas vrais blocks
94
+ # x hide useless editable elements
95
+ # x re-serializer le template
96
+ # ? skipper la methode parse (quoique pas besoin car template non modifie)
97
+ # x snippets
98
+ # x sauvegarder (sans callbacks ??)
99
+ end # loop: pages
100
+ end # loop: locales
101
+ end # loop: sites
102
+ end
103
+
104
+ def self.down
105
+ raise MongoidMigration::IrreversibleMigration
106
+ end
107
+ end
@@ -0,0 +1,107 @@
1
+ class RenameEntryToContentEntry < MongoidMigration::Migration
2
+
3
+ def self.up
4
+ regexp = /(^|Locomotive::)(Content)*Entry([0-9a-fA-F]{24})$/
5
+ replacement = "\\1ContentEntry\\3"
6
+
7
+ # content entries
8
+ self.update_content_entries(regexp, replacement)
9
+
10
+ # content types
11
+ self.update_content_types(regexp, replacement)
12
+
13
+ # templatized pages
14
+ self.update_templatized_pages(regexp, replacement)
15
+ end
16
+
17
+ def self.down
18
+ regexp = /(^|Locomotive::)(Content)+Entry([0-9a-fA-F]{24})$/
19
+ replacement = "\\1Entry\\3"
20
+
21
+ # content entries
22
+ self.update_content_entries(regexp, replacement)
23
+
24
+ # content types
25
+ self.update_content_types(regexp, replacement)
26
+
27
+ # templatized pages
28
+ self.update_templatized_pages(regexp, replacement)
29
+ end
30
+
31
+ private
32
+
33
+ def self.update_content_entries(regexp, replacement)
34
+ self.fetch_rows(Locomotive::ContentEntry) do |collection, attributes|
35
+ recipe = attributes['custom_fields_recipe']
36
+ type = attributes['_type']
37
+ new_recipe = replace_value_by(recipe, regexp, replacement)
38
+ selector = { '_id' => attributes['_id'] }
39
+ operations = { '$set' => {
40
+ '_type' => replace_value_by(type, regexp, replacement),
41
+ 'custom_fields_recipe' => new_recipe
42
+ } }
43
+ collection.update selector, operations
44
+ end
45
+ puts "content entries UPDATED"
46
+ end
47
+
48
+ def self.update_content_types(regexp, replacement)
49
+ self.fetch_rows(Locomotive::ContentType) do |collection, attributes|
50
+ updates = {}
51
+ attributes['entries_custom_fields'].each_with_index do |custom_field, index|
52
+ new_custom_field = replace_value_by(custom_field, regexp, replacement)
53
+ updates["entries_custom_fields.#{index}"] = new_custom_field
54
+ end
55
+
56
+ selector = { '_id' => attributes['_id'] }
57
+ operations = { '$set' => updates }
58
+ collection.update selector, operations
59
+ end
60
+ puts "content types UPDATED"
61
+ end
62
+
63
+ def self.update_templatized_pages(regexp, replacement)
64
+ self.fetch_rows(Locomotive::Page) do |collection, attributes|
65
+ if klass_name = attributes['target_klass_name']
66
+ new_klass_name = replace_value_by(klass_name, regexp, replacement)
67
+ selector = { '_id' => attributes['_id'] }
68
+ operations = { '$set' => { 'target_klass_name' => new_klass_name } }
69
+ collection.update selector, operations
70
+ end
71
+ end
72
+ puts "templatized pages UPDATED"
73
+ end
74
+
75
+ def self.fetch_rows(klass, &block)
76
+ per_page = 100
77
+ collection = klass.collection.driver
78
+ count = collection.count
79
+ num_pages = (count.to_f / per_page).floor
80
+
81
+ # paginate the whole collection to avoid mongodb cursor error
82
+ (0..num_pages).each do |page|
83
+ offset = per_page * page.to_i
84
+ collection.find.sort("_id").limit(per_page).skip(offset).each do |attributes|
85
+ block.call(collection, attributes)
86
+ end
87
+ end
88
+ end
89
+
90
+ def self.replace_value_by(object, regexp, replacement)
91
+ return object.gsub(regexp, replacement) if object.is_a?(String)
92
+
93
+ list = nil
94
+ list = object if object.is_a?(Array)
95
+ list = object.values if object.is_a?(Hash)
96
+
97
+ list.each do |value|
98
+ case value
99
+ when String then value.gsub!(regexp, replacement)
100
+ when Array, Hash then self.replace_value_by(value, regexp, replacement)
101
+ end
102
+ end
103
+
104
+ object
105
+ end
106
+
107
+ end
@@ -7,10 +7,10 @@ describe Locomotive::ContentEntry do
7
7
  before(:each) do
8
8
  Locomotive::Site.any_instance.stubs(:create_default_pages!).returns(true)
9
9
  @content_type = FactoryGirl.build(:content_type)
10
- @content_type.entries_custom_fields.build :label => 'Title', :type => 'string'
11
- @content_type.entries_custom_fields.build :label => 'Description', :type => 'text'
12
- @content_type.entries_custom_fields.build :label => 'Visible ?', :type => 'boolean', :name => 'visible'
13
- @content_type.entries_custom_fields.build :label => 'File', :type => 'file'
10
+ @content_type.entries_custom_fields.build label: 'Title', type: 'string'
11
+ @content_type.entries_custom_fields.build label: 'Description', type: 'text'
12
+ @content_type.entries_custom_fields.build label: 'Visible ?', type: 'boolean', name: 'visible'
13
+ @content_type.entries_custom_fields.build label: 'File', type: 'file'
14
14
  @content_type.valid?
15
15
  @content_type.send(:set_label_field)
16
16
  end
@@ -24,56 +24,57 @@ describe Locomotive::ContentEntry do
24
24
  ## Validations ##
25
25
 
26
26
  it 'requires the presence of title' do
27
- content_entry = build_content_entry :title => nil
27
+ content_entry = build_content_entry title: nil
28
28
  content_entry.should_not be_valid
29
29
  content_entry.errors[:title].should == ["can't be blank"]
30
30
  end
31
31
 
32
32
  it 'requires the presence of the permalink (_slug)' do
33
- content_entry = build_content_entry :title => nil
33
+ content_entry = build_content_entry title: nil
34
34
  content_entry.should_not be_valid
35
35
  content_entry.errors[:_slug].should == ["can't be blank"]
36
36
  end
37
37
 
38
38
  end
39
39
 
40
- context 'setting the slug' do
40
+ describe '#slug' do
41
+
41
42
  before :each do
42
- build_content_entry(:_slug => 'dogs').tap(&:save!)._slug.should == 'dogs'
43
+ build_content_entry(_slug: 'dogs').tap(&:save!)._slug.should == 'dogs'
43
44
  end
44
45
 
45
46
  it 'uses the given slug if it is unique' do
46
- build_content_entry(:_slug => 'monkeys').tap(&:save!)._slug.should == 'monkeys'
47
- build_content_entry(:_slug => 'cats-2').tap(&:save!)._slug.should == 'cats-2'
47
+ build_content_entry(_slug: 'monkeys').tap(&:save!)._slug.should == 'monkeys'
48
+ build_content_entry(_slug: 'cats-2').tap(&:save!)._slug.should == 'cats-2'
48
49
  end
49
50
 
50
51
  it 'appends a number to the end of the slug if it is not unique' do
51
- build_content_entry(:_slug => 'dogs').tap(&:save!)._slug.should == 'dogs-1'
52
- build_content_entry(:_slug => 'dogs').tap(&:save!)._slug.should == 'dogs-2'
53
- build_content_entry(:_slug => 'dogs-2').tap(&:save!)._slug.should == 'dogs-3'
54
- build_content_entry(:_slug => 'dogs-2').tap(&:save!)._slug.should == 'dogs-4'
52
+ build_content_entry(_slug: 'dogs').tap(&:save!)._slug.should == 'dogs-1'
53
+ build_content_entry(_slug: 'dogs').tap(&:save!)._slug.should == 'dogs-2'
54
+ build_content_entry(_slug: 'dogs-2').tap(&:save!)._slug.should == 'dogs-3'
55
+ build_content_entry(_slug: 'dogs-2').tap(&:save!)._slug.should == 'dogs-4'
55
56
  end
56
57
 
57
58
  it 'ignores the case of a slug' do
58
- build_content_entry(:_slug => 'dogs').tap(&:save!)._slug.should == 'dogs-1'
59
- build_content_entry(:_slug => 'DOGS').tap(&:save!)._slug.should == 'dogs-2'
59
+ build_content_entry(_slug: 'dogs').tap(&:save!)._slug.should == 'dogs-1'
60
+ build_content_entry(_slug: 'DOGS').tap(&:save!)._slug.should == 'dogs-2'
60
61
  end
61
62
 
62
63
  it 'correctly handles slugs with multiple numbers' do
63
- build_content_entry(:_slug => 'fish-1-2').tap(&:save!)._slug.should == 'fish-1-2'
64
- build_content_entry(:_slug => 'fish-1-2').tap(&:save!)._slug.should == 'fish-1-3'
64
+ build_content_entry(_slug: 'fish-1-2').tap(&:save!)._slug.should == 'fish-1-2'
65
+ build_content_entry(_slug: 'fish-1-2').tap(&:save!)._slug.should == 'fish-1-3'
65
66
 
66
- build_content_entry(:_slug => 'fish-1-hi').tap(&:save!)._slug.should == 'fish-1-hi'
67
- build_content_entry(:_slug => 'fish-1-hi').tap(&:save!)._slug.should == 'fish-1-hi-1'
67
+ build_content_entry(_slug: 'fish-1-hi').tap(&:save!)._slug.should == 'fish-1-hi'
68
+ build_content_entry(_slug: 'fish-1-hi').tap(&:save!)._slug.should == 'fish-1-hi-1'
68
69
  end
69
70
  end
70
71
 
71
- context '#i18n' do
72
+ describe '#i18n' do
72
73
 
73
74
  before(:each) do
74
75
  localize_content_type @content_type
75
76
  ::Mongoid::Fields::I18n.locale = 'en'
76
- @content_entry = build_content_entry(:title => 'Hello world')
77
+ @content_entry = build_content_entry(title: 'Hello world')
77
78
  ::Mongoid::Fields::I18n.locale = 'fr'
78
79
  end
79
80
 
@@ -90,12 +91,13 @@ describe Locomotive::ContentEntry do
90
91
  end
91
92
 
92
93
  describe "#navigation" do
94
+
93
95
  before(:each) do
94
96
  @content_type.order_by = '_position'
95
97
  @content_type.save
96
98
 
97
99
  %w(first second third).each_with_index do |item, index|
98
- content = build_content_entry(:title => item.to_s, :_position => index)
100
+ content = build_content_entry(title: item.to_s, _position: index)
99
101
  content.save
100
102
  instance_variable_set "@#{item}", content
101
103
  end
@@ -196,24 +198,35 @@ describe Locomotive::ContentEntry do
196
198
  end
197
199
 
198
200
  it 'uses the to_label method if the value of the label field defined it' do
199
- entry = build_content_entry(:_label_field_name => 'with_to_label')
200
- entry.stubs(:with_to_label).returns(mock('with_to_label', :to_label => 'acme'))
201
+ entry = build_content_entry(_label_field_name: 'with_to_label')
202
+ entry.stubs(:with_to_label).returns(mock('with_to_label', to_label: 'acme'))
201
203
  entry._label.should == 'acme'
202
204
  end
203
205
 
204
206
  it 'uses the to_s method at last if the label field did not define the to_label method' do
205
- entry = build_content_entry(:_label_field_name => 'not_a_string')
206
- entry.stubs(:not_a_string).returns(mock('not_a_string', :to_s => 'not_a_string'))
207
+ entry = build_content_entry(_label_field_name: 'not_a_string')
208
+ entry.stubs(:not_a_string).returns(mock('not_a_string', to_s: 'not_a_string'))
207
209
  entry._label.should == 'not_a_string'
208
210
  end
209
211
 
210
212
  end
211
213
 
214
+ describe '#file' do
215
+
216
+ let(:entry) { build_content_entry(title: 'Hello world', file: FixturedAsset.open('5k.png')) }
217
+
218
+ it 'writes the file to the filesystem' do
219
+ entry.save
220
+ entry.file.url.should_not =~ /content_content_entry/
221
+ end
222
+
223
+ end
224
+
212
225
  describe '#public_submission' do
213
226
 
214
227
  before(:each) do
215
- @account_1 = FactoryGirl.build('admin user', :id => fake_bson_id('1'))
216
- @account_2 = FactoryGirl.build('frenchy user', :id => fake_bson_id('2'))
228
+ @account_1 = FactoryGirl.build('admin user', id: fake_bson_id('1'))
229
+ @account_2 = FactoryGirl.build('frenchy user', id: fake_bson_id('2'))
217
230
 
218
231
  @content_type.public_submission_enabled = true
219
232
  @content_type.public_submission_accounts = ['', @account_1._id, @account_2._id.to_s]
@@ -221,7 +234,7 @@ describe Locomotive::ContentEntry do
221
234
  site = FactoryGirl.build(:site)
222
235
  site.stubs(:accounts).returns([@account_1, @account_2])
223
236
 
224
- @content_entry = build_content_entry(:site => site)
237
+ @content_entry = build_content_entry(site: site)
225
238
  end
226
239
 
227
240
  it 'does not send email notifications if the api is disabled' do
@@ -237,8 +250,8 @@ describe Locomotive::ContentEntry do
237
250
  end
238
251
 
239
252
  it 'sends email notifications when a new instance is created' do
240
- Locomotive::Notifications.expects(:new_content_entry).with(@account_1, @content_entry).returns(mock('mailer', :deliver => true))
241
- Locomotive::Notifications.expects(:new_content_entry).with(@account_2, @content_entry).returns(mock('mailer', :deliver => true))
253
+ Locomotive::Notifications.expects(:new_content_entry).with(@account_1, @content_entry).returns(mock('mailer', deliver: true))
254
+ Locomotive::Notifications.expects(:new_content_entry).with(@account_2, @content_entry).returns(mock('mailer', deliver: true))
242
255
  @content_entry.save
243
256
  end
244
257
 
@@ -260,7 +273,7 @@ describe Locomotive::ContentEntry do
260
273
  end
261
274
 
262
275
  def build_content_entry(options = {})
263
- @content_type.entries.build({ :title => 'Locomotive', :description => 'Lorem ipsum....', :_label_field_name => 'title' }.merge(options))
276
+ @content_type.entries.build({ title: 'Locomotive', description: 'Lorem ipsum....', _label_field_name: 'title' }.merge(options))
264
277
  end
265
278
 
266
279
  def fake_bson_id(id)
@@ -22,4 +22,10 @@ describe 'Locomotive::Middlewares::SeoTrailingSlash' do
22
22
  response.status.should be(301)
23
23
  end
24
24
 
25
+ it 'removes the trailing slash but preserves the query' do
26
+ get '/hello_world/?test=name'
27
+ response.status.should be(301)
28
+ response.location.should == '/hello_world?test=name'
29
+ end
30
+
25
31
  end
metadata CHANGED
@@ -2,14 +2,14 @@
2
2
  name: locomotive_cms
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 2.0.1
5
+ version: 2.0.2
6
6
  platform: ruby
7
7
  authors:
8
8
  - Didier Lafforgue
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-04 00:00:00.000000000 Z
12
+ date: 2013-04-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  type: :runtime
@@ -1104,6 +1104,8 @@ files:
1104
1104
  - lib/locomotive_cms.rb
1105
1105
  - lib/tasks/development.rake
1106
1106
  - lib/tasks/locomotive.rake
1107
+ - mongodb/migrate/20130204072721_make_editable_elements_consistent.rb
1108
+ - mongodb/migrate/20130326201349_rename_entry_to_content_entry.rb
1107
1109
  - public/favicon.ico
1108
1110
  - vendor/assets/fonts/locomotive/fontawesome-webfont.eot
1109
1111
  - vendor/assets/fonts/locomotive/fontawesome-webfont.svg
@@ -1337,7 +1339,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
1337
1339
  - !ruby/object:Gem::Version
1338
1340
  segments:
1339
1341
  - 0
1340
- hash: -3303457319512363184
1342
+ hash: -4389909398335603459
1341
1343
  version: '0'
1342
1344
  required_rubygems_version: !ruby/object:Gem::Requirement
1343
1345
  none: false
@@ -1346,7 +1348,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
1346
1348
  - !ruby/object:Gem::Version
1347
1349
  segments:
1348
1350
  - 0
1349
- hash: -3303457319512363184
1351
+ hash: -4389909398335603459
1350
1352
  version: '0'
1351
1353
  requirements: []
1352
1354
  rubyforge_project: