alchemy_cms 2.2.rc8 → 2.2.rc11

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 (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