alchemy_cms 2.0.rc6 → 2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +2 -1
- data/LICENSE +24 -619
- data/README.md +38 -36
- data/alchemy_cms.gemspec +1 -1
- data/app/controllers/admin/elements_controller.rb +3 -2
- data/app/controllers/admin/pages_controller.rb +20 -14
- data/app/controllers/admin/trash_controller.rb +4 -0
- data/app/controllers/pages_controller.rb +27 -16
- data/app/helpers/admin/elements_helper.rb +55 -0
- data/app/helpers/alchemy_helper.rb +0 -45
- data/app/helpers/elements_helper.rb +8 -1
- data/app/helpers/pages_helper.rb +0 -10
- data/app/models/element.rb +39 -5
- data/app/models/page.rb +27 -7
- data/app/sweepers/pages_sweeper.rb +0 -9
- data/app/views/admin/pages/update.js.erb +15 -1
- data/app/views/admin/partials/_upload_form.html.erb +2 -2
- data/app/views/elements/_contactform_view.html.erb +10 -23
- data/app/views/elements/_searchresult_view.html.erb +47 -40
- data/app/views/essences/_essence_html_view.html.erb +1 -1
- data/app/views/pages/show.rss.builder +20 -25
- data/assets/javascripts/alchemy.js +2 -2
- data/assets/stylesheets/elements.css +1 -1
- data/assets/stylesheets/standard_set.css +84 -14
- data/config/alchemy/elements.yml +2 -0
- data/config/alchemy/page_layouts.yml +1 -0
- data/config/locales/de.yml +5 -5
- data/lib/alchemy/version.rb +1 -1
- data/lib/rails/generators/alchemy/plugin/templates/config.yml +3 -3
- data/spec/controllers/admin/trash_controller_spec.rb +22 -0
- data/spec/controllers/pages_controller_spec.rb +39 -0
- data/spec/dummy/db/schema.rb +10 -9
- data/spec/factories.rb +5 -0
- data/spec/helpers/elements_helper_spec.rb +7 -0
- data/spec/integration/pages_controller_spec.rb +1 -1
- data/spec/models/element_spec.rb +51 -5
- data/spec/models/page_spec.rb +70 -10
- metadata +10 -7
- data/TODO.txt +0 -1
data/README.md
CHANGED
@@ -6,32 +6,20 @@ Alchemy CMS
|
|
6
6
|
About
|
7
7
|
-----
|
8
8
|
|
9
|
-
Alchemy is a
|
10
|
-
|
11
|
-
Nearly every content management system stores the content of a page in a body column in the pages table. This is easy to develop and the user manages the content inside one of the fancy new Javascript based wysiwyg processors. Formatting, image placement, styling and positioning of the content is in the hand of the end-user.
|
12
|
-
|
13
|
-
__We think this is completly wrong!__
|
14
|
-
|
15
|
-
The content manager mustn‘t be able to change anything but the content and some basic text formatting. The content manager shouldn‘t care about headline formatting, image positioning or resizing. The developer should take care of this!
|
16
|
-
|
17
|
-
__Alchemy is different!__
|
18
|
-
|
19
|
-
We split the page into logical parts like headlines, paragraphs, images, etc. The only thing we store in the database is text: ids of images and richtext content. Nothing else. No markup (besides basic text formatting inside the richtext elements), no styling, no layout. Pure content!
|
20
|
-
|
21
|
-
This gives the webdeveloper the power and flexibility to implement any kind of layout with the insurance that the content manager is not able to break up the layout.
|
9
|
+
Alchemy is a powerfull Content Management System (CMS) with an extremly flexible content storing architecture.
|
22
10
|
|
23
11
|
Features
|
24
12
|
--------
|
25
13
|
|
26
14
|
- Highly flexible Templating:
|
27
|
-
- Content is stored in small parts not as a complete, monolithic page
|
15
|
+
- Content is stored in small parts not as a complete, monolithic page
|
28
16
|
- The designer chooses the template structure, not the CMS!
|
29
|
-
- Every Design is possible, no templating, or theming restrictions
|
17
|
+
- Every Design is possible, no templating, or theming restrictions
|
30
18
|
- Even Flash® Content Management is possible
|
31
19
|
- Gorgious End-User centric interface:
|
32
|
-
- No
|
20
|
+
- No markup editors
|
33
21
|
- Multilingual:
|
34
|
-
- Create as many (complete independent) language trees as you want
|
22
|
+
- Create as many (complete independent) language trees as you want
|
35
23
|
- URL based language switching
|
36
24
|
- SEO
|
37
25
|
- Every Part of SEO is manageable by the user
|
@@ -41,6 +29,7 @@ Features
|
|
41
29
|
- Rolebased Authentification (RBAS)
|
42
30
|
- Protect pages for restricted access
|
43
31
|
- Fulltext Search
|
32
|
+
- RSS Feeds
|
44
33
|
- Contactforms
|
45
34
|
- Attachments and downloads
|
46
35
|
- Powerfull image rendering
|
@@ -54,74 +43,87 @@ Features
|
|
54
43
|
- Integrates in exsiting Rails Apps
|
55
44
|
- Caching
|
56
45
|
- Completely free:
|
57
|
-
-
|
46
|
+
- BSD License
|
58
47
|
- No Enterprise Licences, or Community Editions
|
59
48
|
- Hostable on any Server that supports RubyOnRails and ImageMagick ([Software Requirements](https://github.com/magiclabs/alchemy/wiki/Software-Requirements))
|
60
49
|
|
61
50
|
Rails Version
|
62
51
|
-------------
|
63
52
|
|
64
|
-
This
|
53
|
+
This version of Alchemy runs with Rails 3.0.10.
|
54
|
+
|
55
|
+
If you are looking for a Rails 2 compatible version check the rails-2 branch.
|
56
|
+
|
57
|
+
A Rails 3.1 compatible beta version can be found in the next_stable branch.
|
58
|
+
|
59
|
+
Ruby Version
|
60
|
+
------------
|
61
|
+
|
62
|
+
Alchemy runs with REE, Ruby 1.8.7, Ruby 1.9.2 and Ruby 1.9.3.
|
65
63
|
|
66
64
|
Installation
|
67
65
|
------------
|
68
66
|
|
69
67
|
Use the installer (recommended):
|
70
68
|
|
71
|
-
gem install alchemy_cms
|
69
|
+
gem install alchemy_cms
|
72
70
|
alchemy new my_magicpage
|
73
71
|
|
74
72
|
Start the local server:
|
75
73
|
|
76
74
|
rails server
|
77
75
|
|
78
|
-
Then just switch to your browser and open `http://localhost:3000
|
76
|
+
Then just switch to your browser and open `http://localhost:3000`
|
77
|
+
|
78
|
+
Add to existing Rails project
|
79
|
+
-----------------------------
|
79
80
|
|
80
|
-
|
81
|
+
In your Gemfile:
|
81
82
|
|
82
|
-
|
83
|
+
gem 'alchemy_cms'
|
84
|
+
|
85
|
+
Run in terminal:
|
83
86
|
|
84
87
|
bundle install
|
85
88
|
rake alchemy:prepare
|
86
|
-
rake alchemy:standard_set:install (optional)
|
87
89
|
rake db:migrate
|
88
90
|
rake db:seed
|
89
91
|
|
90
|
-
|
91
92
|
Tipps
|
92
93
|
-----
|
93
94
|
|
94
|
-
1. This
|
95
|
+
1. This generator creates all necessary folders and files needed for creating your own page layouts and elements for your website:
|
95
96
|
|
96
|
-
|
97
|
+
rails generate alchemy:scaffold
|
97
98
|
|
98
99
|
2. If you use the ferret full text search (enabled by default), then please add a job to your crontab that reindexes the ferret index.
|
99
100
|
|
100
101
|
cd /path/to/your/alchemy && RAILS_ENV=production rake ferret:rebuild_index > /dev/null
|
101
102
|
|
102
|
-
3. You can easily create your element
|
103
|
+
3. You can easily create your element files (for view and editor) depending on the `elements.yml` with this generator:
|
103
104
|
|
104
105
|
rails generate elements
|
105
106
|
|
106
107
|
Resources
|
107
108
|
---------
|
108
109
|
|
109
|
-
* Homepage: <http://alchemy-
|
110
|
-
* Live-Demo: <http://demo.alchemy-
|
111
|
-
* Wiki: <http://wiki.alchemy-
|
112
|
-
* API Documentation: <http://api.alchemy-
|
113
|
-
* Issue-Tracker: <http://issues.alchemy-
|
114
|
-
* Sourcecode: <http://source.alchemy-
|
110
|
+
* Homepage: <http://alchemy-cms.com>
|
111
|
+
* Live-Demo: <http://demo.alchemy-cms.com>
|
112
|
+
* Wiki: <http://wiki.alchemy-cms.com>
|
113
|
+
* API Documentation: <http://api.alchemy-cms.com>
|
114
|
+
* Issue-Tracker: <http://issues.alchemy-cms.com>
|
115
|
+
* Sourcecode: <http://source.alchemy-cms.com>
|
115
116
|
* User Group: <http://groups.google.com/group/alchemy-cms>
|
116
117
|
|
117
118
|
Authors
|
118
119
|
---------
|
119
120
|
|
120
|
-
* Carsten Fregin: <https://github.com/cfregin>
|
121
121
|
* Thomas von Deyen: <https://github.com/tvdeyen>
|
122
122
|
* Robin Böning: <https://github.com/robinboening>
|
123
|
+
* Marc Schettke: <https://github.com/masche842>
|
124
|
+
* Carsten Fregin: <https://github.com/cfregin>
|
123
125
|
|
124
126
|
License
|
125
127
|
-------
|
126
128
|
|
127
|
-
*
|
129
|
+
* BSD: <https://raw.github.com/magiclabs/alchemy_cms/master/LICENSE>
|
data/alchemy_cms.gemspec
CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |s|
|
|
12
12
|
s.summary = %q{An extremly flexbile CMS for Rails 3.}
|
13
13
|
s.description = %q{Alchemy is an awesome Rails CMS with an extremely flexible content storing architecture.}
|
14
14
|
s.requirements << 'ImageMagick (libmagick), v6.6 or greater.'
|
15
|
-
s.license = '
|
15
|
+
s.license = 'BSD New'
|
16
16
|
|
17
17
|
s.files = `git ls-files`.split("\n")
|
18
18
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
@@ -42,8 +42,10 @@ class Admin::ElementsController < AlchemyController
|
|
42
42
|
@page = Page.find(params[:element][:page_id])
|
43
43
|
if params[:paste_from_clipboard].blank?
|
44
44
|
@element = Element.new_from_scratch(params[:element])
|
45
|
+
cell_definition = Cell.definition_for(params[:element][:name].split('#').last)
|
45
46
|
else
|
46
|
-
source_element = Element.find(params[:paste_from_clipboard])
|
47
|
+
source_element = Element.find(params[:paste_from_clipboard].to_i)
|
48
|
+
cell_definition = Cell.definition_for(params[:paste_from_clipboard].split('#').last)
|
47
49
|
if source_element.page_id == blank? # aka. move
|
48
50
|
@element = source_element
|
49
51
|
else
|
@@ -52,7 +54,6 @@ class Admin::ElementsController < AlchemyController
|
|
52
54
|
end
|
53
55
|
# if page has cells, put element in cell
|
54
56
|
if @page.has_cells?
|
55
|
-
cell_definition = Cell.definition_for(params[:element][:name].split('#').last)
|
56
57
|
if cell_definition
|
57
58
|
@cell = @page.cells.find_or_create_by_name(cell_definition['name'])
|
58
59
|
end
|
@@ -73,14 +73,14 @@ class Admin::PagesController < AlchemyController
|
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
76
|
+
def update
|
77
|
+
# fetching page via before filter
|
78
|
+
if @page.update_attributes(params[:page])
|
79
|
+
@notice = _("Page %{name} saved") % {:name => @page.name}
|
80
|
+
else
|
81
|
+
render_remote_errors(@page, "form#edit_page_#{@page.id} button.button")
|
82
|
+
end
|
83
|
+
end
|
84
84
|
|
85
85
|
def destroy
|
86
86
|
# fetching page via before filter
|
@@ -218,12 +218,8 @@ class Admin::PagesController < AlchemyController
|
|
218
218
|
end
|
219
219
|
|
220
220
|
def flush
|
221
|
-
|
222
|
-
|
223
|
-
expire_action("#{page.language_code}/#{page.urlname}")
|
224
|
-
else
|
225
|
-
expire_action("#{page.urlname}")
|
226
|
-
end
|
221
|
+
Page.with_language(session[:language_id]).flushables.each do |page|
|
222
|
+
expire_page(page)
|
227
223
|
end
|
228
224
|
respond_to do |format|
|
229
225
|
format.js
|
@@ -240,4 +236,14 @@ private
|
|
240
236
|
request.raw_post.split('&').map { |i| i = {i.split('=')[0].gsub(/[^0-9]/, '') => i.split('=')[1]} }
|
241
237
|
end
|
242
238
|
|
239
|
+
def expire_page(page)
|
240
|
+
return if page.do_not_sweep
|
241
|
+
expire_action(
|
242
|
+
:controller => '/pages',
|
243
|
+
:action => :show,
|
244
|
+
:urlname => page.urlname_was,
|
245
|
+
:lang => multi_language? ? page.language_code : nil
|
246
|
+
)
|
247
|
+
end
|
248
|
+
|
243
249
|
end
|
@@ -4,11 +4,15 @@ class Admin::TrashController < AlchemyController
|
|
4
4
|
|
5
5
|
before_filter :set_translation
|
6
6
|
|
7
|
+
helper Admin::ElementsHelper
|
8
|
+
|
7
9
|
def index
|
8
10
|
@elements = Element.trashed
|
9
11
|
@page = Page.find_by_id(params[:page_id])
|
10
12
|
@allowed_elements = Element.all_for_page(@page)
|
11
13
|
render :layout => false
|
14
|
+
rescue Exception => e
|
15
|
+
exception_handler(e)
|
12
16
|
end
|
13
17
|
|
14
18
|
def clear
|
@@ -7,12 +7,11 @@ class PagesController < AlchemyController
|
|
7
7
|
|
8
8
|
caches_action(
|
9
9
|
:show,
|
10
|
-
:
|
11
|
-
:
|
12
|
-
:if => Proc.new { |c|
|
10
|
+
:cache_path => proc { url_for(:action => :show, :urlname => params[:urlname], :lang => multi_language? ? params[:lang] : nil) },
|
11
|
+
:if => proc do
|
13
12
|
if Alchemy::Config.get(:cache_pages)
|
14
13
|
page = Page.find_by_urlname_and_language_id_and_public(
|
15
|
-
|
14
|
+
params[:urlname],
|
16
15
|
session[:language_id],
|
17
16
|
true,
|
18
17
|
:select => 'page_layout, language_id, urlname'
|
@@ -24,20 +23,31 @@ class PagesController < AlchemyController
|
|
24
23
|
else
|
25
24
|
false
|
26
25
|
end
|
27
|
-
|
26
|
+
end
|
28
27
|
)
|
29
28
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
29
|
+
# Showing page from params[:urlname]
|
30
|
+
# @page is fetched via before filter
|
31
|
+
# @root_page is fetched via before filter
|
32
|
+
# @language fetched via before_filter in alchemy_controller
|
33
|
+
# rendering page and querying for search results if any query is present
|
34
|
+
def show
|
35
|
+
if configuration(:ferret) && !params[:query].blank?
|
36
|
+
perform_search
|
37
|
+
end
|
38
|
+
respond_to do |format|
|
39
|
+
format.html {
|
40
|
+
render :layout => params[:layout].blank? ? 'pages' : params[:layout] == 'none' ? false : params[:layout]
|
41
|
+
}
|
42
|
+
format.rss {
|
43
|
+
if @page.contains_feed?
|
44
|
+
render :action => "show.rss.builder", :layout => false
|
45
|
+
else
|
46
|
+
render :xml => { :error => 'Not found' }, :status => 404
|
47
|
+
end
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
41
51
|
|
42
52
|
# Renders a Google conform sitemap in xml
|
43
53
|
def sitemap
|
@@ -97,6 +107,7 @@ private
|
|
97
107
|
{:limit => :all},
|
98
108
|
{:conditions => ["public = ?", true]}
|
99
109
|
)
|
110
|
+
@search_results = (@text_search_results + @rtf_search_results).sort{ |y, x| x.ferret_score <=> y.ferret_score }
|
100
111
|
end
|
101
112
|
|
102
113
|
def find_first_public(page)
|
@@ -56,4 +56,59 @@ module Admin::ElementsHelper
|
|
56
56
|
)
|
57
57
|
end
|
58
58
|
|
59
|
+
def clipboard_select_tag(items, html_options = {})
|
60
|
+
options = [[_('Please choose'), ""]]
|
61
|
+
items.each do |item|
|
62
|
+
options << [item.class.to_s == 'Element' ? item.display_name_with_preview_text : item.name, item.id]
|
63
|
+
end
|
64
|
+
select_tag(
|
65
|
+
'paste_from_clipboard',
|
66
|
+
@page.has_cells? ? grouped_elements_for_select(items, :id) : options_for_select(options),
|
67
|
+
{
|
68
|
+
:class => html_options[:class] || 'very_long',
|
69
|
+
:style => html_options[:style]
|
70
|
+
}
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns all elements that could be placed on that page because of the pages layout.
|
75
|
+
# The elements are returned as an array to be used in alchemy_selectbox form builder.
|
76
|
+
def elements_for_select(elements)
|
77
|
+
return [] if elements.nil?
|
78
|
+
options = elements.collect{ |e| [I18n.t("alchemy.element_names.#{e['name']}", :default => e['name'].capitalize), e["name"]] }
|
79
|
+
return options_for_select(options)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns all elements that could be placed on that page because of the pages layout.
|
83
|
+
# The elements will be grouped by cell.
|
84
|
+
def grouped_elements_for_select(elements, object_method = 'name')
|
85
|
+
return [] if elements.nil?
|
86
|
+
cells_definition = Cell.definitions
|
87
|
+
return [] if cells_definition.blank?
|
88
|
+
options = {}
|
89
|
+
celled_elements = []
|
90
|
+
cells_definition.each do |cell|
|
91
|
+
cell_elements = elements.select { |e| cell['elements'].include?(e.class.name == 'Element' ? e.name : e['name']) }
|
92
|
+
celled_elements += cell_elements
|
93
|
+
optgroup_label = Cell.translated_label_for(cell['name'])
|
94
|
+
options[optgroup_label] = cell_elements.map do |e|
|
95
|
+
[
|
96
|
+
I18n.t("alchemy.element_names.#{e['name']}", :default => e['name'].capitalize),
|
97
|
+
(e.class.name == 'Element' ? e.send(object_method).to_s : e[object_method]) + "##{cell['name']}"
|
98
|
+
]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
other_elements = elements - celled_elements
|
102
|
+
unless other_elements.blank?
|
103
|
+
optgroup_label = _('other Elements')
|
104
|
+
options[optgroup_label] = other_elements.map do |e|
|
105
|
+
[
|
106
|
+
I18n.t("alchemy.element_names.#{e['name']}", :default => e['name'].capitalize),
|
107
|
+
e.class.name == 'Element' ? e.send(object_method) : e[object_method]
|
108
|
+
]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
return grouped_options_for_select(options)
|
112
|
+
end
|
113
|
+
|
59
114
|
end
|
@@ -101,51 +101,6 @@ module AlchemyHelper
|
|
101
101
|
filter_field << "</div>"
|
102
102
|
filter_field.html_safe
|
103
103
|
end
|
104
|
-
|
105
|
-
def clipboard_select_tag(items, html_options = {})
|
106
|
-
options = [[_('Please choose'), ""]]
|
107
|
-
items.each do |item|
|
108
|
-
options << [item.class.to_s == 'Element' ? item.display_name_with_preview_text : item.name, item.id]
|
109
|
-
end
|
110
|
-
select_tag(
|
111
|
-
'paste_from_clipboard',
|
112
|
-
options_for_select(options),
|
113
|
-
{
|
114
|
-
:class => html_options[:class] || 'very_long',
|
115
|
-
:style => html_options[:style]
|
116
|
-
}
|
117
|
-
)
|
118
|
-
end
|
119
|
-
|
120
|
-
# Returns all elements that could be placed on that page because of the pages layout.
|
121
|
-
# The elements are returned as an array to be used in alchemy_selectbox form builder.
|
122
|
-
def elements_for_select(elements)
|
123
|
-
return [] if elements.nil?
|
124
|
-
options = elements.collect{ |e| [I18n.t("alchemy.element_names.#{e['name']}", :default => e['name'].capitalize), e["name"]] }
|
125
|
-
return options_for_select(options)
|
126
|
-
end
|
127
|
-
|
128
|
-
# Returns all elements that could be placed on that page because of the pages layout.
|
129
|
-
# The elements will be grouped by cell.
|
130
|
-
def grouped_elements_for_select(elements)
|
131
|
-
return [] if elements.nil?
|
132
|
-
cells_definition = Cell.definitions
|
133
|
-
return [] if cells_definition.blank?
|
134
|
-
options = {}
|
135
|
-
celled_elements = []
|
136
|
-
cells_definition.each do |cell|
|
137
|
-
cell_elements = elements.select { |e| cell['elements'].include?(e['name']) }
|
138
|
-
celled_elements += cell_elements
|
139
|
-
optgroup_label = Cell.translated_label_for(cell['name'])
|
140
|
-
options[optgroup_label] = cell_elements.map { |e| [I18n.t("alchemy.element_names.#{e['name']}", :default => e['name'].capitalize), e['name'] + "##{cell['name']}"] }
|
141
|
-
end
|
142
|
-
other_elements = elements - celled_elements
|
143
|
-
unless other_elements.blank?
|
144
|
-
optgroup_label = _('other Elements')
|
145
|
-
options[optgroup_label] = other_elements.map { |e| [I18n.t("alchemy.element_names.#{e['name']}", :default => e['name'].capitalize), e['name']] }
|
146
|
-
end
|
147
|
-
return grouped_options_for_select(options)
|
148
|
-
end
|
149
104
|
|
150
105
|
def link_to_confirmation_window(link_string = "", message = "", url = "", html_options = {})
|
151
106
|
title = _("please_confirm")
|
@@ -20,6 +20,7 @@ module ElementsHelper
|
|
20
20
|
:except => [],
|
21
21
|
:only => [],
|
22
22
|
:from_page => "",
|
23
|
+
:from_cell => "",
|
23
24
|
:count => nil,
|
24
25
|
:offset => nil,
|
25
26
|
:locals => {},
|
@@ -53,7 +54,7 @@ module ElementsHelper
|
|
53
54
|
element_string = ""
|
54
55
|
if options[:fallback]
|
55
56
|
unless all_elements.detect { |e| e.name == options[:fallback][:for] }
|
56
|
-
if from = Page.
|
57
|
+
if from = Page.find_by_page_layout_and_language_id(options[:fallback][:from], session[:language_id])
|
57
58
|
all_elements += from.elements.find_all_by_name(options[:fallback][:with].blank? ? options[:fallback][:for] : options[:fallback][:with])
|
58
59
|
end
|
59
60
|
end
|
@@ -143,6 +144,12 @@ module ElementsHelper
|
|
143
144
|
return element
|
144
145
|
end
|
145
146
|
|
147
|
+
# Renders all element partials from given cell.
|
148
|
+
def render_cell_elements(cell)
|
149
|
+
return warning("No cell given.") if cell.blank?
|
150
|
+
render_elements({:from_cell => cell})
|
151
|
+
end
|
152
|
+
|
146
153
|
# Returns a string for the id attribute of a html element for the given element
|
147
154
|
def element_dom_id(element)
|
148
155
|
return "" if element.nil?
|