alchemy_cms 2.2.rc8 → 2.2.rc11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/README.md +1 -1
  2. data/app/assets/javascripts/alchemy/alchemy.page_sorter.js +1 -1
  3. data/app/assets/stylesheets/alchemy/sitemap.css.scss +14 -13
  4. data/app/assets/stylesheets/alchemy/standard_set.css +9 -10
  5. data/app/controllers/alchemy/admin/pages_controller.rb +1 -0
  6. data/app/controllers/alchemy/base_controller.rb +2 -1
  7. data/app/helpers/alchemy/admin/essences_helper.rb +1 -0
  8. data/app/helpers/alchemy/elements_helper.rb +2 -10
  9. data/app/helpers/alchemy/essences_helper.rb +1 -0
  10. data/app/helpers/alchemy/pages_helper.rb +31 -13
  11. data/app/models/alchemy/attachment.rb +1 -1
  12. data/app/models/alchemy/content.rb +8 -8
  13. data/app/models/alchemy/element.rb +14 -10
  14. data/app/models/alchemy/essence_file.rb +1 -1
  15. data/app/models/alchemy/message.rb +9 -9
  16. data/app/models/alchemy/page.rb +4 -3
  17. data/app/models/alchemy/picture.rb +1 -1
  18. data/app/models/alchemy/user.rb +1 -1
  19. data/app/sweepers/alchemy/content_sweeper.rb +29 -6
  20. data/app/views/alchemy/admin/attachments/new.html.erb +1 -1
  21. data/app/views/alchemy/admin/pages/index.html.erb +7 -7
  22. data/app/views/alchemy/admin/pages/sort.js.erb +2 -1
  23. data/app/views/alchemy/admin/partials/_upload_form.html.erb +2 -2
  24. data/app/views/alchemy/admin/pictures/new.html.erb +1 -1
  25. data/app/views/alchemy/essences/_essence_picture_tools.html.erb +2 -2
  26. data/app/views/alchemy/essences/_essence_text_editor.html.erb +11 -11
  27. data/app/views/alchemy/navigation/_link.html.erb +20 -20
  28. data/app/views/alchemy/navigation/_renderer.html.erb +37 -23
  29. data/config/alchemy/config.yml +107 -65
  30. data/config/alchemy/elements.yml +12 -1
  31. data/config/locales/alchemy.de.yml +1 -0
  32. data/config/locales/alchemy.en.yml +1 -0
  33. data/lib/alchemy/essence.rb +1 -1
  34. data/lib/alchemy/page_layout.rb +3 -9
  35. data/lib/alchemy/resource.rb +5 -5
  36. data/lib/alchemy/version.rb +1 -1
  37. data/lib/rails/generators/alchemy/scaffold/scaffold_generator.rb +2 -2
  38. data/lib/rails/generators/alchemy/scaffold/{files/page_layouts.yml → templates/page_layouts.yml.tt} +4 -0
  39. data/spec/config_spec.rb +1 -1
  40. data/spec/dummy/db/schema.rb +1 -1
  41. data/spec/helpers/admin/contents_helper_spec.rb +4 -3
  42. data/spec/helpers/admin/essences_helper_spec.rb +4 -8
  43. data/spec/helpers/essences_helper_spec.rb +0 -4
  44. data/spec/helpers/pages_helper_spec.rb +10 -1
  45. data/spec/integration/admin/modules_integration_spec.rb +25 -21
  46. data/spec/integration/admin/pages_controller_spec.rb +16 -19
  47. data/spec/integration/admin/resources_integration_spec.rb +58 -64
  48. data/spec/integration/pages_controller_spec.rb +24 -3
  49. data/spec/integration/security_spec.rb +12 -12
  50. data/spec/libraries/resource_spec.rb +16 -18
  51. data/spec/models/page_spec.rb +352 -326
  52. data/spec/page_layout_spec.rb +2 -2
  53. data/spec/support/alchemy/specs_helpers.rb +16 -1
  54. metadata +116 -38
  55. data/spec/support/integration_spec_helper.rb +0 -24
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  Alchemy CMS
2
2
  ===========
3
3
 
4
- [![Build Status](https://secure.travis-ci.org/magiclabs/alchemy_cms.png?branch=next_stable)](http://travis-ci.org/magiclabs/alchemy_cms)
4
+ [![Build Status](https://secure.travis-ci.org/magiclabs/alchemy_cms.png?branch=master)](http://travis-ci.org/magiclabs/alchemy_cms)
5
5
  ![Status](http://stillmaintained.com/magiclabs/alchemy_cms.png)
6
6
 
7
7
  About
@@ -32,7 +32,7 @@ if (typeof(Alchemy) === 'undefined') {
32
32
  $.post(Alchemy.routes.order_admin_pages_path, params);
33
33
  return false;
34
34
  });
35
- $('#bottom_buttons .button').click(Alchemy.pleaseWaitOverlay);
35
+ $('#sort_panel .button').click(Alchemy.pleaseWaitOverlay);
36
36
  Alchemy.PageSorter.disableButton();
37
37
  Alchemy.resizeFrame();
38
38
  },
@@ -1,22 +1,23 @@
1
1
  @import "alchemy/defaults";
2
2
 
3
- div#bottom_panel {
3
+ div#sort_panel {
4
+ background: $medium-gray;
5
+ padding: 60px 0 8px 0;
4
6
  position: fixed;
5
- bottom: 0;
6
- right: 0;
7
- width: 100%;
7
+ top: 27px;
8
+ left: 0;
8
9
  z-index: 10;
9
- background: $medium-gray image-url('alchemy/shading.png') repeat-x 0 0;
10
- }
10
+ width: 100%;
11
+ border-bottom: $default-border;
11
12
 
12
- div#bottom_panel div.info {
13
- margin: 16px 16px 8px 81px;
14
- width: 720px;
15
- }
13
+ div.info {
14
+ margin: 8px 8px 8px 104px;
15
+ width: 720px;
16
+ }
16
17
 
17
- div#bottom_buttons {
18
- padding: 0 16px 16px;
19
- margin-left: 65px;
18
+ div.buttons {
19
+ margin: 0 0 8px 104px;
20
+ }
20
21
  }
21
22
 
22
23
  .sitemap_pagename_link {
@@ -146,18 +146,17 @@ div#header_image {
146
146
  background-color: #ffa300;
147
147
  }
148
148
 
149
- #navigation ul.navigation_level_1 li {
149
+ #navigation ul.navigation.level_1 li {
150
150
  display: inline;
151
151
  float: left;
152
152
  }
153
153
 
154
- #navigation ul.navigation_level_2 li,
155
- #navigation ul.navigation_level_-1 li {
154
+ #navigation ul.navigation.level_2 li {
156
155
  display: block;
157
156
  float: none;
158
157
  }
159
158
 
160
- #navigation ul.navigation_level_3 li {
159
+ #navigation ul.navigation.level_3 li {
161
160
 
162
161
  }
163
162
 
@@ -165,7 +164,7 @@ div#header_image {
165
164
  background-color: #c0c0c0;
166
165
  }
167
166
 
168
- #navigation ul.navigation_level_2 > li.last {
167
+ #navigation ul.navigation.level_2 > li.last {
169
168
 
170
169
  }
171
170
 
@@ -181,7 +180,7 @@ div#header_image {
181
180
  color: #fff;
182
181
  }
183
182
 
184
- #navigation ul.navigation_level_2, #navigation ul.navigation_level_-1 {
183
+ #navigation ul.navigation.level_2 {
185
184
  display: none;
186
185
  position: absolute;
187
186
  z-index: 10;
@@ -191,22 +190,22 @@ div#header_image {
191
190
  clear: both;
192
191
  }
193
192
 
194
- #navigation ul.navigation_level_3 {
193
+ #navigation ul.navigation.level_3 {
195
194
  display: none;
196
195
  z-index: 20;
197
196
  background-color: white;
198
197
  float: none;
199
198
  }
200
199
 
201
- #navigation ul.navigation_level_3 li {
200
+ #navigation ul.navigation.level_3 li {
202
201
  padding-left: 20px;
203
202
  }
204
203
 
205
- #navigation ul.navigation_level_1 li:hover ul.navigation_level_2, #navigation ul.navigation_level_1 li:hover ul.navigation_level_-1 {
204
+ #navigation ul.navigation.level_1 li:hover ul.navigation.level_2 {
206
205
  display: block;
207
206
  }
208
207
 
209
- #navigation ul.navigation_level_2 li:hover ul.navigation_level_3 {
208
+ #navigation ul.navigation.level_2 li:hover ul.navigation.level_3 {
210
209
  display: block;
211
210
  }
212
211
 
@@ -10,6 +10,7 @@ module Alchemy
10
10
  filter_access_to [:index, :link, :layoutpages, :new, :switch_language, :create, :move, :flush], :attribute_check => false
11
11
 
12
12
  cache_sweeper Alchemy::PagesSweeper, :only => [:publish], :if => proc { Alchemy::Config.get(:cache_pages) }
13
+ cache_sweeper Alchemy::ContentSweeper, :only => [:create, :update, :destroy]
13
14
 
14
15
  def index
15
16
  @page_root = Page.language_root_for(session[:language_id])
@@ -129,7 +129,8 @@ module Alchemy
129
129
  if exception
130
130
  logger.info "Rendering 404: #{exception.message}"
131
131
  end
132
- render :file => "#{Rails.root}/public/404", :status => 404
132
+ @page = Page.language_root_for(session[:language_id])
133
+ render :file => "#{Rails.root}/public/404", :status => 404, :layout => !@page.nil?
133
134
  end
134
135
 
135
136
  protected
@@ -46,6 +46,7 @@ module Alchemy
46
46
  # Renders the Content editor partial from the given Element by position (e.g. 1).
47
47
  # For options see -> render_essence
48
48
  def render_essence_editor_by_position(element, position, options = {})
49
+ ActiveSupport::Deprecation.warn 'Alchemy CMS: render_essence_editor_by_position is not supported anymore and will be removed.'
49
50
  if element.blank?
50
51
  warning('Element is nil')
51
52
  return ""
@@ -59,7 +59,6 @@ module Alchemy
59
59
  unless options[:sort_by].blank?
60
60
  all_elements = all_elements.sort_by { |e| e.contents.detect { |c| c.name == options[:sort_by] }.ingredient }
61
61
  end
62
- all_elements.reverse! if options[:reverse_sort] || options[:reverse]
63
62
  element_string = ""
64
63
  if options[:fallback]
65
64
  unless all_elements.detect { |e| e.name == options[:fallback][:for] }
@@ -88,9 +87,6 @@ module Alchemy
88
87
  }
89
88
  options = default_options.merge(options)
90
89
  element.store_page(@page) if part == :view
91
- path1 = "#{Rails.root}/app/views/elements/"
92
- path2 = "#{Rails.root}/vendor/plugins/alchemy/app/views/elements/"
93
- partial_name = "_#{element.name.underscore}_#{part}.html.erb"
94
90
  locals = options.delete(:locals)
95
91
  render(
96
92
  :partial => "alchemy/elements/#{element.name.underscore}_#{part}",
@@ -101,14 +97,10 @@ module Alchemy
101
97
  }.merge(locals || {})
102
98
  )
103
99
  end
104
- rescue ActionView::MissingTemplate
100
+ rescue ActionView::MissingTemplate => e
105
101
  warning(%(
106
102
  Element #{part} partial not found for #{element.name}.\n
107
- Looking for #{partial_name}, but not found
108
- neither in #{path1}
109
- nor in #{path2}
110
- Use rails generate alchemy:elements to generate them.
111
- Maybe you still have old style partial names? (like .rhtml). Then please rename them in .html.erb'
103
+ #{e}
112
104
  ))
113
105
  render :partial => "alchemy/elements/#{part}_not_found", :locals => {:name => element.name, :error => "Element #{part} partial not found. Use rails generate alchemy:elements to generate them."}
114
106
  end
@@ -83,6 +83,7 @@ module Alchemy
83
83
  # <%= render_essence_view_by_type(element, 2) %>
84
84
  #
85
85
  def render_essence_view_by_position(element, position, options = {}, html_options = {})
86
+ ActiveSupport::Deprecation.warn 'Alchemy CMS: render_essence_view_by_position is not supported anymore and will be removed.'
86
87
  if element.blank?
87
88
  warning('Element is nil')
88
89
  return ""
@@ -112,12 +112,12 @@ module Alchemy
112
112
 
113
113
  # Renders the navigation.
114
114
  #
115
- # It produces a html <ul><li></li></ul> structure with all necessary classes and ids so you can produce every navigation the web uses today.
115
+ # It produces a html <ul><li></li></ul> structure with all necessary classes so you can produce every navigation the web uses today.
116
116
  # I.E. dropdown-navigations, simple mainnavigations or even complex nested ones.
117
117
  #
118
118
  # === En detail:
119
119
  #
120
- # <ul class="navigation_level_1">
120
+ # <ul class="navigation level_1">
121
121
  # <li class="first home"><a href="/home" class="active" title="Homepage" lang="en" data-page-id="1">Homepage</a></li>
122
122
  # <li class="contact"><a href="/contact" title="Contact" lang="en" data-page-id="2">Contact</a></li>
123
123
  # <li class="last imprint"><a href="/imprint" title="Imprint" lang="en" data-page-id="3">Imprint</a></li>
@@ -143,7 +143,15 @@ module Alchemy
143
143
  # :reverse => false # Reverse the navigation
144
144
  # :reverse_children => false # Reverse the nested children
145
145
  #
146
- def render_navigation(options = {})
146
+ # === Passing HTML classes and ids to the renderer
147
+ #
148
+ # A second hash will be passed as html_options to the navigation renderer partial.
149
+ #
150
+ # ==== Example:
151
+ #
152
+ # <%= render_navigation({from_page => 'subnavi'}, {:class => 'navigation', :id => 'subnavigation'}) %>
153
+ #
154
+ def render_navigation(options = {}, html_options = {})
147
155
  default_options = {
148
156
  :submenu => false,
149
157
  :all_sub_menues => false,
@@ -179,7 +187,12 @@ module Alchemy
179
187
  if options[:reverse]
180
188
  pages.reverse!
181
189
  end
182
- render :partial => options[:navigation_partial], :locals => {:options => options, :pages => pages}
190
+ render(
191
+ options[:navigation_partial],
192
+ :options => options,
193
+ :pages => pages,
194
+ :html_options => html_options
195
+ )
183
196
  end
184
197
 
185
198
  # Renders navigation the children and all siblings of the given page (standard is the current page).
@@ -217,6 +230,12 @@ module Alchemy
217
230
  @breadcrumb.include?(page)
218
231
  end
219
232
 
233
+ # Returns +'active'+ if the given external page is in the current url path or +nil+.
234
+ def external_page_css_class(page)
235
+ return nil if !page.redirects_to_external?
236
+ request.path.split('/').delete_if(&:blank?).first == page.urlname.gsub(/^\//, '') ? 'active' : nil
237
+ end
238
+
220
239
  # Returns page links in a breadcrumb beginning from root to current page.
221
240
  #
222
241
  # === Options:
@@ -293,7 +312,7 @@ module Alchemy
293
312
  :seperator => ""
294
313
  }
295
314
  default_options.update(options)
296
- [default_options[:prefix], @page.title].join(default_options[:seperator])
315
+ [default_options[:prefix], response.status == 200 ? @page.title : response.status].join(default_options[:seperator])
297
316
  end
298
317
 
299
318
  # Returns a complete html <title> tag for the <head> part of the html document.
@@ -306,11 +325,10 @@ module Alchemy
306
325
  def render_title_tag(options={})
307
326
  default_options = {
308
327
  :prefix => "",
309
- :seperator => "|"
328
+ :seperator => ""
310
329
  }
311
330
  options = default_options.merge(options)
312
- title = render_page_title(options)
313
- %(<title>#{title}</title>).html_safe
331
+ %(<title>#{render_page_title(options)}</title>).html_safe
314
332
  end
315
333
 
316
334
  # Renders a html <meta> tag for :name => "" and :content => ""
@@ -358,7 +376,7 @@ module Alchemy
358
376
  end
359
377
  default_options = {
360
378
  :title_prefix => "",
361
- :title_seperator => "|",
379
+ :title_seperator => "",
362
380
  :default_lang => "de"
363
381
  }
364
382
  options = default_options.merge(options)
@@ -378,15 +396,15 @@ module Alchemy
378
396
  meta_string = %(
379
397
  <meta charset="UTF-8">
380
398
  #{render_title_tag(:prefix => options[:title_prefix], :seperator => options[:title_seperator])}
381
- #{render_meta_tag(:name => "description", :content => description)}
382
- #{render_meta_tag(:name => "keywords", :content => keywords)}
399
+ #{render_meta_tag(:name => "description", :content => description)}
400
+ #{render_meta_tag(:name => "keywords", :content => keywords)}
383
401
  <meta name="created" content="#{@page.updated_at}">
384
402
  <meta name="robots" content="#{robot}">
385
403
  )
386
404
  if @page.contains_feed?
387
405
  meta_string += %(
388
- <link rel="alternate" type="application/rss+xml" title="RSS" href="#{show_alchemy_page_url(@page, :protocol => 'feed', :format => :rss)}">
389
- )
406
+ <link rel="alternate" type="application/rss+xml" title="RSS" href="#{show_alchemy_page_url(@page, :protocol => 'feed', :format => :rss)}">
407
+ )
390
408
  end
391
409
  return meta_string.html_safe
392
410
  end
@@ -3,7 +3,7 @@ module Alchemy
3
3
 
4
4
  attr_accessible :uploaded_data, :name
5
5
 
6
- stampable
6
+ stampable(:stamper_class_name => 'Alchemy::User')
7
7
 
8
8
  has_attachment(
9
9
  :storage => :file_system,
@@ -6,7 +6,7 @@ module Alchemy
6
6
  belongs_to :essence, :polymorphic => true, :dependent => :destroy
7
7
  belongs_to :element
8
8
 
9
- stampable :stamper_class_name => :user
9
+ stampable(:stamper_class_name => 'Alchemy::User')
10
10
 
11
11
  acts_as_list
12
12
 
@@ -55,9 +55,9 @@ module Alchemy
55
55
  # Settings from the elements.yml definition
56
56
  def settings
57
57
  return {} if description.blank?
58
- settings = description['settings']
59
- return {} if settings.blank?
60
- settings.symbolize_keys
58
+ @settings ||= description['settings']
59
+ return {} if @settings.blank?
60
+ @settings.symbolize_keys
61
61
  end
62
62
 
63
63
  def siblings
@@ -104,11 +104,11 @@ module Alchemy
104
104
  logger.warn("\n+++++++++++ Warning: Content with id #{self.id} is missing its Element\n")
105
105
  return nil
106
106
  else
107
- desc = self.element.content_description_for(self.name)
108
- if desc.blank?
109
- desc = self.element.available_content_description_for(self.name)
107
+ @desc ||= self.element.content_description_for(self.name)
108
+ if @desc.blank?
109
+ @desc ||= self.element.available_content_description_for(self.name)
110
110
  else
111
- return desc
111
+ return @desc
112
112
  end
113
113
  end
114
114
  end
@@ -13,7 +13,7 @@ module Alchemy
13
13
 
14
14
  # All Elements inside a cell are a list. All Elements not in cell are in the cell_id.nil list.
15
15
  acts_as_list :scope => [:page_id, :cell_id]
16
- stampable :stamper_class_name => :user
16
+ stampable(:stamper_class_name => 'Alchemy::User')
17
17
 
18
18
  has_many :contents, :order => :position, :dependent => :destroy
19
19
  belongs_to :cell
@@ -121,22 +121,26 @@ module Alchemy
121
121
  contents.find_by_name(rss_title['name'])
122
122
  end
123
123
 
124
- # Inits a new element for page as described in /config/alchemy/elements.yml from element_name
124
+ # Builds a new element as described in +/config/alchemy/elements.yml+
125
125
  def self.new_from_scratch(attributes)
126
126
  attributes.stringify_keys!
127
127
  return Element.new if attributes['name'].blank?
128
128
  element_descriptions = Element.descriptions
129
129
  return if element_descriptions.blank?
130
- element_scratch = element_descriptions.select { |m| m["name"] == attributes['name'].split('#').first }.first
131
- element = Element.new(
132
- element_scratch.except('contents', 'available_contents', 'display_name', 'amount').merge({
133
- :page_id => attributes['page_id']
134
- })
135
- )
136
- element
130
+ element_name = attributes['name'].split('#').first
131
+ element_scratch = element_descriptions.detect { |m| m["name"] == element_name }
132
+ if element_scratch
133
+ Element.new(
134
+ element_scratch.except('contents', 'available_contents', 'display_name', 'amount').merge({
135
+ :page_id => attributes['page_id']
136
+ })
137
+ )
138
+ else
139
+ raise "Element description for #{element_name} not found. Please check your elements.yml"
140
+ end
137
141
  end
138
142
 
139
- # Inits a new element for page as described in /config/alchemy/elements.yml from element_name and saves it
143
+ # Builds a new element as described in +/config/alchemy/elements.yml+ and saves it
140
144
  def self.create_from_scratch(attributes)
141
145
  element = Element.new_from_scratch(attributes)
142
146
  element.save if element
@@ -1,7 +1,7 @@
1
1
  module Alchemy
2
2
  class EssenceFile < ActiveRecord::Base
3
3
 
4
- attr_accessible :title, :css_class
4
+ attr_accessible :title, :css_class, :attachment_id
5
5
 
6
6
  acts_as_essence(
7
7
  :ingredient_column => :attachment,
@@ -1,13 +1,13 @@
1
1
  # This is a tableless model only used for validating contactform fields.
2
- #
2
+ #
3
3
  # You can specify the fields for your contactform in the +config/alchemy/config.yml+ file in the +:mailer+ options.
4
- #
4
+ #
5
5
  # === Example Contactform Configuration:
6
- #
7
- # :mailer:
8
- # :form_layout_name: contact
9
- # :fields: [subject, name, email, message, info]
10
- # :validate_fields: [name, email]
6
+ #
7
+ # mailer:
8
+ # form_layout_name: contact
9
+ # fields: [subject, name, email, message, info]
10
+ # validate_fields: [name, email]
11
11
 
12
12
  module Alchemy
13
13
  class Message
@@ -22,12 +22,12 @@ module Alchemy
22
22
  attr_accessor :contact_form_id, :ip
23
23
  attr_accessible :contact_form_id
24
24
 
25
- @@config[:fields].each do |field|
25
+ @@config['fields'].each do |field|
26
26
  attr_accessor field.to_sym
27
27
  attr_accessible field.to_sym
28
28
  end
29
29
 
30
- @@config[:validate_fields].each do |field|
30
+ @@config['validate_fields'].each do |field|
31
31
  validates_presence_of field
32
32
  if field.to_s == 'email'
33
33
  validates_format_of field, :with => ::Authlogic::Regex.email, :if => :email_is_filled
@@ -30,7 +30,7 @@ module Alchemy
30
30
  RESERVED_URLNAMES = %w(admin messages)
31
31
 
32
32
  acts_as_nested_set(:dependent => :destroy)
33
- stampable
33
+ stampable(:stamper_class_name => 'Alchemy::User')
34
34
 
35
35
  has_many :folded_pages
36
36
  has_many :cells, :dependent => :destroy
@@ -64,7 +64,7 @@ module Alchemy
64
64
  scope :not_locked, where(:locked => false)
65
65
  scope :visible, where(:visible => true)
66
66
  scope :published, where(:public => true)
67
- scope :accessable, where(:restricted => false)
67
+ scope :accessible, where(:restricted => false)
68
68
  scope :restricted, where(:restricted => true)
69
69
  scope :not_restricted, where(:restricted => false)
70
70
  scope :public_language_roots, lambda {
@@ -107,6 +107,7 @@ module Alchemy
107
107
  elsif !options[:except].blank?
108
108
  elements = elements.excluded(options[:except])
109
109
  end
110
+ elements = elements.reverse_order if options[:reverse_sort] || options[:reverse]
110
111
  elements = elements.offset(options[:offset]).limit(options[:count])
111
112
  elements = elements.order("RAND()") if options[:random]
112
113
  if show_non_public
@@ -260,7 +261,7 @@ module Alchemy
260
261
 
261
262
  def fold(user_id, status)
262
263
  folded_page = FoldedPage.find_or_create_by_user_id_and_page_id(user_id, self.id)
263
- folded_page.update_attributes(:folded => status)
264
+ folded_page.folded = status
264
265
  folded_page.save
265
266
  end
266
267